Learn Dart - Null Safety & Type System
Series/Learn Dart/Episode 6
Episode 6 of 23

Learn Dart - Null Safety & Type System

This episode dissects Dart's type system and null safety: the difference between nullable and non-nullable, the ?. and ?? operators, late initialization, type promotion, and how sound null safety and migration tools work.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The null-reference error — called the billion dollar mistake by Tony Hoare — is the most common source of bugs in many languages. Dart answers it with sound null safety: the type system strictly distinguishes values that may be null from those that may not, and the compiler enforces this from compile time.

Episode 6 dissects this mechanism thoroughly. You'll learn to mark nullable types with a question mark, use null-aware operators, understand late, leverage type promotion, and follow the migration path for legacy code.

With sound null safety, a whole category of bugs disappears even before the program runs.

Nullable vs Non-Nullable Types

Question Mark for Nullable

By default, types in Dart are non-nullable. To allow null, add a question mark:

Nullable and non-nullable
void main() {
  String nama = 'Dart';       // tidak boleh null
  String? namaOpsional;       // boleh null
 
  namaOpsional = null;        // valid
  print(namaOpsional);
}

String? namaOpsional indicates that the value can be null or a String. The compiler will reject code that uses a nullable variable without a check, because its value isn't guaranteed to be available.

Bang Operator to Assert Non-null

If you're sure a nullable value isn't null at some point, use the ! operator:

Bang operator
void main() {
  String? hasil;
 
  hasil = 'selesai';
 
  if (hasil != null) {
    print(hasil.length);
  }
}

Above, the if block narrows hasil's type from nullable to non-nullable — this is called type promotion. Use ! only when you're truly certain, because an incorrect assertion can still cause a runtime error.

Null-Aware Operators

Safe Navigation ?.

Access properties and methods without risk of error using ?.:

Safe navigation
void main() {
  String? nama;
  print(nama?.length);   // null, bukan error
  print(nama?.toUpperCase() ?? 'tidak ada');
}

nama?.length returns null when nama is null, instead of throwing an exception. This operator eliminates long chains of null checks.

Null Coalescing ?? and ??=

The ?? operator provides a fallback value, and ??= assigns if the value is null:

Null-aware operators
void main() {
  String? konfigurasi;
 
  var nilai = konfigurasi ?? 'default';
  konfigurasi ??= 'diset sekali';
 
  print(nilai);
  print(konfigurasi);
}

konfigurasi ??= 'diset sekali' only assigns if konfigurasi is still null. The combination of ?., ??, and ??= makes null handling code expressive and safe.

Late Initialization and Type Promotion

late for Deferred Initialization

Sometimes a field can't be set in the constructor, for example when it depends on data that only becomes available later. Use late:

Late initialization
class Pengaturan {
  late String koneksi;
 
  void muat() {
    koneksi = 'redis://localhost:6379';
  }
}
 
void main() {
  var p = Pengaturan();
  p.muat();
  print(p.koneksi);
}

late String koneksi defers initialization until muat() is called. The compiler guarantees that a late field is always initialized before being read.

Type Promotion on Local Variables

Dart narrows types automatically after a check. This works especially well for local variables and private parameters:

Type promotion
void proses(Object input) {
  if (input is String) {
    print(input.toUpperCase());
  } else if (input is int) {
    print(input + 1);
  }
}
 
void main() {
  proses('halo');
  proses(41);
}

Inside the input is String block, the compiler treats input as a String, so the toUpperCase method is available without a manual cast. Promotion significantly reduces boilerplate code.

Sound Null Safety and Migration Tools

What Sound Means

Sound means the type guarantees hold across the entire program, including when using legacy libraries or native interop. The compiler and runtime will never find null where a type is statically non-nullable. The consequence: more aggressive optimization paths and smaller, faster apps.

Migrating Legacy Code

For projects created before null safety (Dart below 2.12), use the official migration tool:

Run the migration tool
dart migrate

dart migrate analyzes the entire project and prepares type changes interactively. When done, make sure the SDK constraint in pubspec.yaml is at least sdk: ^2.12.0 and run dart analyze to confirm there are no errors.

Conclusion

Key takeaways:

  • Non-nullable types can't hold null; add ? to make them nullable.
  • The ! operator forces a non-null value only if you're certain.
  • ?. accesses without errors; ?? provides a fallback; ??= assigns if null.
  • late defers field initialization; the compiler still guarantees its safety.
  • Type promotion narrows types automatically after an is check.
  • Migrate legacy projects with dart migrate, then verify with dart analyze.

In the next episode 7, we'll cover asynchronous programming — using Future with async and await, error handling, the basics of Stream with listeners and transformations, the Isolate model for concurrency, and asynchronous I/O use cases in real applications.

Learn Dart - Null Safety & Type System | Learn Dart