Skip to content

Null Safety

Dart has sound null safety: a type cannot hold null unless you explicitly say so with ?. This is a hard guarantee, not a lint — if the type system says a value is non-null, it genuinely cannot be null at runtime.

int a = 1; // non-nullable — can never be null
int? b = null; // nullable — the ? opts in to null
a = null; // ❌ compile error: a cannot be null
b = 42; // ✅ a nullable can also hold a real value

“Sound” is the key word: because the guarantee is airtight, the compiler can optimize on it and you can trust it. A String name widget property will never surprise you with a null.

String? maybeName;
// ?. — null-aware access: call only if not null, else the whole thing is null
final len = maybeName?.length; // int? — null if maybeName is null
// ?? — if-null: provide a fallback when the left side is null
final safe = maybeName ?? 'Guest'; // String — never null
// ??= — assign only if currently null
maybeName ??= 'Default';
// ! — null assertion: "I promise this isn't null" (throws if you're wrong)
final definite = maybeName!; // String — DANGER if it's actually null

The ! operator is the one to respect: it silences the compiler by asserting non-null, and if you’re wrong it throws at runtime. Every ! is a small bet — use it only when you can prove the value is non-null.

You rarely need !, because Dart’s flow analysis narrows a nullable to non-null after a check:

void greet(String? name) {
if (name == null) return;
// From here down, Dart KNOWS name is String (not String?) — no ! needed.
print(name.toUpperCase());
}
flowchart TB
  start["name: String?"] --> check{"name == null?"}
  check -->|yes| ret["return early"]
  check -->|no| promoted["name: String
(promoted, safe to use)"]
Flow analysis promotes a nullable to non-null

There’s a catch that bites Flutter devs: flow analysis works on local variables, not on instance fields or getters (they could change between the check and the use). For a nullable field, copy it to a local first, then check the local.

late says “this will be non-null by the time anyone reads it, just not at declaration.” Useful for values set in initState or computed once.

late final String config; // no value yet, but promised before first read
// If you read it before assigning, you get a LateInitializationError.

Use late sparingly — it trades a compile-time guarantee for a runtime one. A misused late is just a ! in disguise.

What does "sound" null safety mean in Dart?
What does the `??` operator do?
Why is the `!` (null assertion) operator risky?
Why does flow analysis sometimes fail to promote a nullable field?