skip to content

In Dart collection literals, how do the spread ..., the null-aware spread ...? and the Dart 3.8 null-aware element ?x each handle null?

level: middleimportance: should knowfreq 48%

answer

  1. null of the collection vs null inside it
  2. plain ... needs a non-nullable Iterable
  3. ...? skips a null source only
  4. ?x drops one null value
  5. map literals: ?key and ?value

basics

~20 s

Plain ...xs needs a non-nullable iterable; ...?xs inserts nothing when xs itself is null but still copies null elements inside it; the Dart 3.8 null-aware element ?x inserts one value only when it is not null.

solid answer

~40 s

The three operate at different levels. `...xs` requires `xs` to have a non-nullable `Iterable` type; spreading a `List<String>?` is a compile error. `...?xs` accepts a nullable collection and contributes nothing when it is `null`, but it does not filter: `...?[1, null, 3]` still inserts the `null`. The **null-aware element** `?x`, added in Dart 3.8 and requiring an SDK lower bound of `3.8`, works on a single value: it inserts `x` only when `x` is not `null`. In map literals the marker can go on the key, the value, or both, and a null on a marked side drops the whole entry, so `{'page': ?page}` is empty when `page` is null. It replaces `if (x != null) x`, and the `use_null_aware_elements` lint suggests the rewrite.

code

dart · 10 lines
dart
List<int>? a = null;
var b = [1, null, 3];
print([0, ...?a, ...?b, 4]); // [0, 1, null, 3, 4]

String? subtitle;
int? page;
var params = {'q': 'flutter', 'page': ?page}; // {q: flutter}
var labels = ['Title', ?subtitle, 'Footer']; // [Title, Footer]
print(params);
print(labels);

go deeper

for a junior

Remember that plain ... rejects a nullable list at compile time and that ...? inserts nothing for a null list.

for a middle

Separate the null collection from a null value: ...? handles the first, ?x the second, and neither filters nulls inside a collection. Know the map entry rules.

for a senior

Check SDK constraints before adopting ?x across packages, migrate if (x != null) x forms with the use_null_aware_elements lint, and watch for unnecessary markers the analyzer flags.

for a principal

Decide when to raise a shared package's SDK lower bound to 3.8 to unlock null-aware elements, weighing cleaner literals against consumers still on older SDKs.

## Two different kinds of null A collection literal can meet `null` in two places, and Dart gives each its own syntax: - the **source collection** of a spread may itself be `null` (an optional parameter such as `List<String>? extraOptions`); - a **single value** you want to insert may be `null` (a `String? subtitle`, a `Widget? adminTile`). Confusing the two is the usual mistake: `...?` handles the first, `?x` the second, and neither removes `null` values that sit inside a non-null collection. ## Plain spread: `...` `...expression` inserts every element of an `Iterable` (or, in a map literal, every entry of a `Map`). Under sound null safety the operand's static type must be **non-nullable**: ```dart List<String> buildCommandLine( String executable, List<String> options, [ List<String>? extraOptions, ]) { return [ executable, ...options, ...?extraOptions, // plain ...extraOptions would not compile ]; } ``` Writing `...extraOptions` there is rejected by the analyzer because the expression might be `null`. ## Null-aware spread: `...?` `...?expression` accepts a nullable collection: - when the collection is `null`, the spread contributes **zero elements**; - when it is not `null`, it behaves exactly like `...`, including copying any `null` elements it contains. So with `List<int>? a = null` and `var b = [1, null, 3]`, the literal `[0, ...?a, ...?b, 4]` is `[0, 1, null, 3, 4]`. Using `...?` on an operand that cannot be null makes the analyzer report the operator as unnecessary (`invalid_null_aware_operator`). ## Null-aware element: `?x` (Dart 3.8) A **null-aware element** marks a single expression element with a leading `?`: 1. evaluate the expression; 2. if the result is `null`, insert nothing; 3. otherwise insert the value, typed as non-nullable. It works in list, set and map literals. In a map the marker can prefix the key, the value, or both; if any marked side is `null`, the **whole entry** is left out: | Literal (`page` is `null`) | Result | |---|---| | `{'page': page}` | `{page: null}` | | `{'page': ?page}` | `{}` | | `{?key: 1}` with `key` null | `{}` | | `[1, ?page, 3]` | `[1, 3]` | A typical Flutter use is a list that should include an optional widget: `children: [title, ?subtitleWidget, trailing]`. Another is building query parameters or JSON where absent optional values must not appear as `null` keys. ## A Flutter example Optional widgets are the everyday case. A menu row may show a trailing count only when there is one, and an admin tile only for admins: ```dart Widget? adminTile = isAdmin ? const ListTile(title: Text('Users')) : null; Widget? unreadBadge = unread > 0 ? Badge(label: Text('$unread')) : null; Row(children: [const Text('Inbox'), ?unreadBadge]); ListView(children: [homeTile, settingsTile, ?adminTile, ...?pluginTiles]); ``` The literal type stays `List<Widget>`: `?unreadBadge` inserts a non-null `Widget` or nothing, and `...?pluginTiles` handles a `List<Widget>?` that a caller may not supply. Without the markers, `unreadBadge` would be a compile error in a `List<Widget>` because its type is nullable. ## Choosing between them | Situation | Use | |---|---| | Source collection is non-nullable | `...xs` | | Source collection may be `null` | `...?xs` | | One value may be `null` | `?x` | | Remove `null` values inside a collection | none of these: filter with `whereType` or a `for` element with an `if` | ## Before Dart 3.8 The same intent was spelled with a collection `if`: - `if (x != null) x` works when `x` is a local variable that flow analysis can promote; - for a getter or a non-promotable field, `if (obj.x case final v?) v` binds a non-null copy through a pattern. The `use_null_aware_elements` lint, added in 3.8, flags those `if` forms and suggests `?x`. Because the feature is gated by **language version**, a package whose `pubspec.yaml` declares `sdk: ^3.7.0` cannot use it even when compiled with a newer SDK; the lower bound must be `3.8` or higher. ## Mistakes to avoid - Expecting `...?` to strip `null` elements out of the source. - Writing `?x` in a package whose SDK constraint is below 3.8. - Using `...?` or `?x` on something that can never be `null`, which the analyzer flags as unnecessary.

  • How did Dart code omit a nullable value from a literal before 3.8, and when does the old form still matter?
    With `if (x != null) x`, which relies on promotion of a local, or `if (obj.x case final v?) v` for getters and fields that do not promote. The old form still matters in packages whose SDK constraint is below 3.8, because null-aware elements are gated by language version.
  • What does the analyzer do if you write ...? or ?x on an expression that can never be null?
    It reports `invalid_null_aware_operator`, telling you the null-aware marker is unnecessary because the element, key, value or spread operand cannot be null. Remove the `?` or fix the type if it was meant to be nullable.

saying these in an interview costs you the question

  • The null-aware spread ...? removes null elements from inside the spread collection
  • Null-aware elements like ?x work in any package on Dart 3.0 or later
  • Spreading a nullable list with plain ... just yields nothing at runtime when it is null
  • In a map literal the null-aware marker can only go on the key
  • ?x inserts a null placeholder that the widget later ignores