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.

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.
By default, types in Dart are non-nullable. To allow null, add a question mark:
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.
If you're sure a nullable value isn't null at some point, use the ! 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.
Access properties and methods without risk of error using ?.:
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.
The ?? operator provides a fallback value, and ??= assigns if the value is null:
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.
Sometimes a field can't be set in the constructor, for example when it depends on data that only becomes available later. Use late:
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.
Dart narrows types automatically after a check. This works especially well for local variables and private parameters:
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 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.
For projects created before null safety (Dart below 2.12), use the official migration tool:
dart migratedart 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.
Key takeaways:
? to make them nullable.! 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.is check.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.