In Dart, how does flow analysis promote a nullable local variable after a null check or an is test?
answer
- check, then the type narrows
- early return promotes the rest
- && and || short-circuit
- is and is! narrow too
- Never-returning helpers count as exits
basics
~20 sIn Dart, flow analysis tracks every path through a function: after middle != null, or after if (middle == null) return, a local String? is treated as String wherever only non-null values can reach. An is test promotes the same way.
solid answer
~40 sPromotion is Dart's flow analysis narrowing a local variable's type where it can prove something about the value. For a `String? middle`, inside `if (middle != null) { … }` — or anywhere after `if (middle == null) return …;` — `middle` has type `String`, so `middle.length` compiles with no `!`. It also works through short-circuits: in `middle != null && middle.isNotEmpty` the right operand sees `String`, and after `if (middle == null || middle.isEmpty) return;` the rest of the function does too. `is` tests narrow the same way: after `if (value is! String) return;` an `Object value` is a `String`. Exits count — `return`, `throw`, `break`, and calls to functions returning `Never`. It applies to local variables and parameters, and since Dart 3.2 to private final fields.
code
dart · 15 linesNever fail(String message) => throw ArgumentError(message);
String initials(String first, String? middle, String last) {
if (middle == null || middle.isEmpty) {
// middle is String in the right operand of ||
return '${first[0]}${last[0]}';
}
// middle is String from here on
return '${first[0]}${middle[0]}${last[0]}';
}
String describe(Object value) {
if (value is! String) fail('expected a String, got ${value.runtimeType}');
return value.toUpperCase(); // value promoted to String by the Never call
}go deeper
Recall that checking a local String? for null makes it a String inside the checked branch and after an early return, with no ! needed.
Explain which constructs promote — &&, ||, ?:, early exits, is and is!, Never-returning calls — and which kinds of variables are eligible.
Show you structure code around early exits and Never helpers so flow analysis proves non-null, instead of scattering ! through the logic.
Treat promotion as an API design aid: return non-nullable types from validation helpers so callers get proof, not assertions.
## What promotion is **Type promotion** is the compiler narrowing the static type of a variable at a particular point in the code, because **flow analysis** has proved that only values of the narrower type can reach that point. With null safety its most common form is **null-check promotion**: a variable declared `String?` is treated as `String` wherever the analysis knows it is not `null`. It is not a runtime conversion. The value is the same object; the compiler simply lets you use the members of the narrower type without `!` or a cast. ## What triggers it Using the optional middle name, `String? middle`: | Code | Where `middle` is `String` | |---|---| | `if (middle != null) { … }` | inside the then-branch | | `if (middle == null) return x;` | everywhere after the `if` | | `middle == null ? 'none' : middle.trim()` | in the second branch | | `middle != null && middle.isNotEmpty` | in the right operand of `&&` | | `if (middle == null \|\| middle.isEmpty) return;` | in the right operand of `\|\|` and after the `if` | | `middle = 'Ann';` | after the assignment of a non-null value | The same machinery handles **`is` tests**. After `if (value is String)` an `Object value` is a `String` inside the branch, and after `if (value is! String) return;` it is a `String` for the rest of the function. Promotion to a subtype works for any type, not just nullability. ## Exits are part of the proof Flow analysis does **reachability analysis**: it knows which statements can only be reached if an earlier test failed or passed. Anything that stops a path counts as an exit: - `return`, `throw`, `break` and `continue`; - a call to a function whose return type is **`Never`**, such as a helper `Never fail(String msg) => throw StateError(msg);`; - `rethrow` inside a catch. So `if (middle == null) fail('no middle name');` promotes `middle` afterwards just as a `return` would. Writing your own `Never`-returning helpers is how you extend the analysis. ## What can be promoted 1. **Local variables and parameters** — the everyday case. 2. **Private final fields**, since **Dart 3.2**, when nothing else in the library could override them. 3. **Not** public fields, non-final fields, getters, top-level or static variables, or `this`. For those, copy the value into a local first. ## Why it matters in interviews Promotion is what makes null-safe code readable. Most code already checks for `null` before using a value; flow analysis turns that **dynamic correctness into static proof**, so the check itself removes the need for `!`. Candidates who add `!` after a check they already wrote are showing they do not know the check promoted the variable — or that the variable is one of the kinds that cannot be promoted, which is a question worth asking in its own right. ## Mistakes to avoid - Writing `if (middle != null) print(middle!.length);` — the `!` is redundant, and the analyzer says so. - Expecting promotion from a check on one variable to carry over to another variable holding the same value. - Assuming that a check on `widget.middleName` or any other property promotes it. It does not; see field promotion.
- In Dart, why does if (middle == null) fail('missing'); promote middle afterwards when fail is a plain function call?Because `fail` is declared to return `Never`. Flow analysis treats a call whose static type is `Never` as an exit, like `return` or `throw`, so code after the `if` is reachable only when `middle` was not `null`.
- In Dart, does promotion work on a variable declared with var and initialized from a nullable expression?Yes. `var middle = person.middleName;` infers `String?` for a local, and locals promote, so `if (middle != null)` narrows it to `String`. That is exactly why copying a property into a local is the standard fix when the property itself cannot be promoted.
Promotion is like a bouncer who checks your ID at the door: once you are inside, staff treat you as verified without asking again. Leave the room, or let someone swap places with you, and the check no longer counts.
saying these in an interview costs you the question
- You still need ! after checking a local variable for null.
- Promotion converts the value into a new non-nullable object at run time.
- Only if statements promote; && and early returns do not.
- Checking someObject.field != null promotes any field it reaches.
- is tests promote only nullable types, not other subtypes.