skip to content

Bang & Coalescing Operators

The ?., ?? and ??= operators handle a nullable value, while ! asserts it is non-null and throws a TypeError if wrong. Interviewers ask when a ! quietly hands the null risk back to you.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Dart, what do the ?., ?? and ??= operators do, and when is each one the right tool?

level: juniorimportance: must knowfreq 72%

answer

  1. skip, substitute, fill in
  2. ?. yields null on a null receiver
  3. ?? evaluates its right side lazily
  4. ??= assigns only when null
  5. ?? sits below || in precedence

basics

~20 s

In Dart, a?.b reads b only when a is non-null and otherwise yields null; a ?? b yields a unless it is null, evaluating b only then; a ??= b assigns b only when a is currently null.

solid answer

~40 s

`?.` is conditional member access: `profile?.settings` is `null` when `profile` is `null`, otherwise `profile.settings`, so its type is nullable. `??` is the if-null operator: `theme ?? 'system'` gives `theme` unless it is `null`, and only then evaluates the right operand; when that operand is non-nullable, the result is non-nullable. `??=` assigns only when the target is currently `null` — `_cache ??= load()` fills a cache once and leaves an existing value alone — and the expression evaluates to the variable's resulting value. They combine naturally: `profile?.settings?.theme ?? 'system'` walks a chain of optional values and ends in a real `String`. Watch precedence: `??` binds more loosely than arithmetic and `||`, so `count ?? 0 + 1` means `count ?? (0 + 1)`.

code

dart · 25 lines
dart
class Settings {
  Settings({this.theme, this.fontScale});
  String? theme;
  double? fontScale;
}

class Profile {
  Profile({this.settings});
  final Settings? settings;
}

Profile? loadProfile(bool signedIn) =>
    signedIn ? Profile(settings: Settings(theme: 'dark')) : null;

void main() {
  final profile = loadProfile(true);
  final guest = loadProfile(false);

  print(profile?.settings?.theme ?? 'system'); // dark
  print(guest?.settings?.theme ?? 'system');   // system

  profile?.settings?.fontScale ??= 1.0;         // fills the missing value
  profile?.settings?.fontScale ??= 2.0;         // no effect: already 1.0
  print(profile?.settings?.fontScale);          // 1.0
}

go deeper

for a junior

Recall the three meanings: ?. skips on null, ?? substitutes a default, ??= fills a null variable, and that ?? ends a chain with a non-null value.

for a middle

Explain the lazy right operand of ?? and ??=, the result types, and the precedence of ?? relative to arithmetic and the conditional operator.

for a senior

Show you pick defaults deliberately, avoid double evaluation with ?? over ternaries, and parenthesise mixed expressions so reviewers never guess.

for a principal

Decide where defaults belong in a codebase, at the data boundary or at each use, so the same missing value is not defaulted differently in two places.

## Three operators, three jobs The **null-aware operators** let you work with nullable values without first writing an `if`. Each does one job: | Operator | Name | Reads as | Result type | |---|---|---|---| | `a?.b` | conditional member access | "`a.b`, unless `a` is null" | nullable | | `a ?? b` | if-null | "`a`, or `b` if `a` is null" | non-nullable when `b` is | | `a ??= b` | null-aware assignment | "set `a` to `b` if `a` is null" | the variable's resulting value | ## ?. — skip the access `profile?.settings` evaluates `profile`; if it is `null`, the member access is skipped and the whole expression is `null`. If not, it is ordinary `profile.settings`. The type of the result is always nullable, because the skip path yields `null`. - It works for getters, setters and method calls: `profile?.save()` calls `save` only on a real profile. - Its companions `?[]` (subscript) and `?..` (cascade) follow the same rule, and a chain of them **short-circuits** as a unit. - Using `?.` on a receiver that cannot be null draws an analyzer warning (`invalid_null_aware_operator`), which is a hint that the type is stricter than you thought. ## ?? — substitute a default `a ?? b` evaluates `a`. If it is non-null, that is the result and **`b` is never evaluated**. Only when `a` is `null` does Dart evaluate `b`. This laziness matters when `b` is expensive or has side effects. The static type follows: with `String? theme`, `theme ?? 'system'` has type `String`. That is how a chain of optional reads ends in a value you can use directly: ```dart final theme = profile?.settings?.theme ?? 'system'; // String ``` If the left operand can never be null, the analyzer warns (`dead_null_aware_expression`: the right operand is never executed). ## ??= — fill in once `a ??= b` assigns `b` to `a` only when `a` is currently `null`; otherwise it leaves `a` unchanged and does not evaluate `b`. The expression's value is `a`'s value afterwards. Typical uses: 1. **Memoizing**: `_avatar ??= decodeAvatar(bytes);` computes on first use only. 2. **Defaults on optional parameters**: `limit ??= 20;` at the top of a function. 3. **Map entries**: `counts[key] ??= 0;` creates an entry only when the lookup returns `null`. The `unnecessary_null_aware_assignments` lint (recommended set) flags `x ??= null`, which can never do anything. ## Precedence traps In Dart's precedence table, `??` sits **below** arithmetic, comparison, `&&` and `||`, and **above** the conditional `?:` and assignment. So: - `count ?? 0 + 1` is `count ?? (0 + 1)` — write `(count ?? 0) + 1` for the other meaning. - `'Hi ' + name ?? 'guest'` parses as `('Hi ' + name) ?? 'guest'` and fails to compile, because `+` receives a `String?`. Use interpolation with the fallback inside: `'Hi ${name ?? 'guest'}'`. - `a ?? b ? x : y` means `(a ?? b) ? x : y`. Parentheses cost nothing and remove the doubt. ## Choosing between them - The value is optional and you only want to act if it exists → `?.`. - You need a real value and a sensible default exists → `??`. - A variable should hold a default or computed value from now on → `??=`. - No sensible default exists and absence is a bug → none of these; see the `!` operator and explicit throws.

  • In Dart, what is the difference between a ?? b and a != null ? a : b?
    For a local variable they behave the same. For any other expression, `??` evaluates `a` once, while the conditional evaluates it twice — a getter or method call runs twice and may return different values. `??` is also shorter, and the `prefer_if_null_operators` lint in the recommended set suggests it.
  • In Dart, what does the expression x ??= compute() evaluate to when x is already non-null?
    It evaluates to the current value of `x`, and `compute()` is never called. When `x` is `null`, `compute()` runs, its result is stored in `x`, and that value is the result of the whole expression.

saying these in an interview costs you the question

  • The right operand of ?? is always evaluated, then discarded if unused.
  • a?.b throws when a is null, just more politely than a.b.
  • a ??= b overwrites a with b whenever b is non-null.
  • count ?? 0 + 1 adds one to the count or to zero.
  • ?? treats empty strings and zero as missing values too.
open as a page

In Dart, what does the postfix ! operator do at run time, and why does it hand the null risk back to you?

level: middleimportance: must knowfreq 64%

basics

~20 s

In Dart, the postfix ! casts a nullable expression to its non-nullable type; if the value is null it throws a TypeError, 'Null check operator used on a null value'. The compiler stops checking, so a wrong assumption becomes a runtime crash.

open as a page

In Dart, what is null-shorting in a ?. chain, and how do ?[], ?.. and callback?.call() follow the same rule?

level: middleimportance: should knowfreq 42%

basics

~20 s

In Dart, when the receiver of ?. is null, the entire rest of the chain is skipped and the expression is null, so a?.b.c needs no second ?. when b is non-nullable. ?[], ?.. and callback?.call() short-circuit the same way.

open as a page

In Dart code review, how do you decide between a ?? fallback, a descriptive throw, and ! for a value that is nullable in its type?

level: seniorimportance: should knowfreq 38%

basics

~20 s

In Dart, ask whether null is a normal state: if so, handle it with ?? and a truthful default or a guard; if it means a bug, fail loudly with ?? (throw StateError('...')). Reserve ! for invariants proven right beside it.

open as a page