Dart (3.13) Module 5 – Collections and iterables
Collection if, for and spread in Dart
Build Dart lists, sets and maps with collection if, for and spread, use null-aware elements from 3.8, and know when a const literal is a shared object.
What you will learn
- Build lists, sets and maps with collection if, collection for and nested elements
- Merge another collection with spread and skip a null source with null-aware spread
- Remove a null element or map entry with the null-aware element added in Dart 3.8
- Write const collections and recognise when two of them share one object
- Choose the type of a bare literal with a context or an explicit type argument
Before you start
What you’ll learn · Before you start
What you will learn
- Build lists, sets and maps with collection if, collection for and nested elements
- Merge another collection with spread and skip a null source with null-aware spread
- Remove a null element or map entry with the null-aware element added in Dart 3.8
- Write const collections and recognise when two of them share one object
- Choose the type of a bare literal with a context or an explicit type argument
Before you start
On this page
A side menu for a shop app has a fixed order and three choices to make at each page load: whether the cart has anything in it, whether the person is signed in and what the categories are today. Writing those choices as menu.add(...) calls spreads the structure across eight lines of code; Dart lets you say it in one list literal instead.
You will first put control flow inside a list literal with collection if and collection for, then inline another collection with spread and skip a null source with null-aware spread. The null-aware element added in Dart 3.8 drops a null from a list or a map in one character. The last two sections turn a const literal into a shared, deeply immutable object and show when element-type inference needs a nudge.
A collection if adds a piece only when a condition is true
Dart lets a list, set or map literal hold more than values and MapEntrys: it also accepts collection if: An if element inside a Dart list, set or map literal. It writes zero or one element into the surrounding collection and may carry an else branch, so one literal can turn a piece on or off from the data. and collection for: A for element inside a Dart list, set or map literal, with the shape for (var x in xs) e or a C-style for. It walks the loop and adds the element its body builds to the surrounding collection, zero or more times. as elements of its own. A collection if adds an element when its condition is true, drops it when the condition is false, and may carry an else with a second element. This program builds the side menu of a shop app in one literal, and prints it for a guest and for a signed-in user with three items in her cart:
// menu.dart (Dart 3.13.5): a side-menu for a shop app, built as one list
// literal. 'Settings' and 'Sign out' appear only for signed-in users, and
// every category of the shop becomes a tile through a collection for.
void main() {
// Pretend input: a non-signed-in guest, and a signed-in user with a cart.
final categories = <String>['Daily staples', 'Fruit', 'Dairy', 'Household'];
List<String> menuFor({required bool signedIn, int cartItems = 0}) {
return <String>[
'Home',
for (final c in categories) 'Shop: $c',
if (cartItems > 0) 'Cart ($cartItems)' else 'Cart',
if (signedIn) 'Settings',
if (signedIn) 'Sign out' else 'Sign in',
'About',
];
}
final guest = menuFor(signedIn: false);
final asha = menuFor(signedIn: true, cartItems: 3);
print('Guest menu (${guest.length} items):');
for (final item in guest) {
print(' $item');
}
print('');
print('Asha, signed in with 3 items in her cart (${asha.length} items):');
for (final item in asha) {
print(' $item');
}
} Output
Guest menu (8 items): Home Shop: Daily staples Shop: Fruit Shop: Dairy Shop: Household Cart Sign in About Asha, signed in with 3 items in her cart (9 items): Home Shop: Daily staples Shop: Fruit Shop: Dairy Shop: Household Cart (3) Settings Sign out About
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run menu.dart
Read the output against the literal. The guest sees eight items; Asha sees nine, three of them different. The source did not grow: the choices did. Step through the literal to see each piece decide for itself:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- The literal starts empty. menuFor is called with signedIn: false and cartItems: 0. Dart opens an empty List<String> and will drop or add each piece of the literal, in order, from top to bottom.
- A plain string joins the list. The first piece is the ordinary expression 'Home'. Nothing decides whether it belongs: Dart simply adds it, so the list now has one item.
- A for repeats one piece. for (final c in categories) 'Shop: $c' is a collection for: Dart walks the four categories and adds one 'Shop: ...' string each time. The literal has one line, and the list grows by four items.
- An if chooses between two. if (cartItems > 0) 'Cart (3)' else 'Cart' adds exactly one item: Dart checks cartItems > 0. For the guest it is 0, so the else branch runs and 'Cart' joins the list.
- An if with no else drops it. if (signedIn) 'Settings' is a collection if with no else. For the guest signedIn is false, so this piece is dropped and the list length does not change.
- if-else picks the sign-in label. if (signedIn) 'Sign out' else 'Sign in' adds one of the two. signedIn is still false, so 'Sign in' joins the list, which is now seven items long.
- A last string closes it. The last piece is the plain string 'About', always added. The guest's menu is eight items long, in exactly the order of the literal, and the whole literal evaluated once.
- Signed in: two more items. A second call with signedIn: true, cartItems: 3 goes through the same literal: 'Cart (3)' wins the first if, 'Settings' is added, and 'Sign out' replaces 'Sign in', so the list is nine items long. The source does not grow, only the choices change.
Three ideas sit inside this literal:
- A plain string,
'Home', is an expression element and always joins the list. - A collection for,
for (final c in categories) 'Shop: $c', adds one element per step of its loop. The Dart documentation says a for element “iterates and repeatedly evaluates a given inner element, inserting zero or more resulting values”. With four categories, this one line added four items. - A collection if,
if (signedIn) 'Settings', adds its element when the condition is true. The two-branch form,if (cartItems > 0) 'Cart (3)' else 'Cart', always adds exactly one of the two.
The Dart specification treats these as a kind of element too, so they nest inside each other: for (final c in categories) if (c.startsWith('Fruit')) 'Fruit shop' adds one tile only when the loop reaches the fruit category. The Flutter widget tree is a List<Widget>, and this is how a widget list usually turns options on and off: no builder method, no extra variable for a half-built list.
Reaching for add inside the literal
Writing [... if (signedIn) menu.add('Settings')] is a double mistake. add changes a list in place and returns void, so the literal tries to splice a void into itself and the compiler refuses it. Keep the branch inside the literal (if (signedIn) 'Settings'); let the literal decide what joins it.
The same literal becomes testable in one go: a function that returns it takes the two inputs the choices need (signedIn and cartItems), and tests pass the pairs they want to check.
Try it
Change signedIn to true and add if (cartItems == 0) 'Cart is empty' else 'Cart ($cartItems)' instead of the current line. The guest’s menu now says exactly what the cart is doing, in words.
The language specification groups collection if, collection for and spread under the name “control-flow elements” and allows them to nest inside each other, so richer rules compose without turning the literal into a function.
Key idea
Collection if and collection for are elements of a literal, not statements that return one: write them in place and let the literal choose what goes into the list, the set or the map.
Checkpoint
Check: Collection if and for inside a literal
Spread inlines another collection, and skips a null source
A spread element: An ... followed by an Iterable, written inside a Dart list, set or map literal. It inserts every value of the source into the surrounding collection, in order, so one literal can build on another., ...xs, inserts every value of another Iterable into the surrounding collection, in order. Spread works in lists, sets and maps alike (for a map, the source has to be a map, so its entries can be spread). The null-aware spread: A ...? followed by a nullable collection, inside a Dart list, set or map literal. A null source is treated as empty: no element is inserted, and no guard is needed before the spread., ...?xs, does the same but treats null as an empty source; without it, spreading a nullable value is a compile-time error. This program builds a query-parameter map from a few optional fields, mixing spread, null-aware spread, collection if and the null-aware map entries added in Dart 3.8:
// query_params.dart (Dart 3.13.5): building a query-parameter map for a
// search request from optional fields, with spread, null-aware spread and
// the null-aware map entries added in 3.8.
void main() {
// Two defaults every call should carry, then per-request overrides.
const defaults = <String, String>{
'format': 'json',
'page_size': '20',
};
Map<String, String> search({
String? q,
int? page,
List<String>? tags,
Map<String, String>? extra,
}) {
return <String, String>{
...defaults,
...?extra,
'q': ?q,
if (page != null) 'page': '$page',
if (tags != null && tags.isNotEmpty) 'tags': tags.join(','),
};
}
final empty = search();
final guest = search(q: 'moong dal');
final asha = search(
q: 'moong dal',
page: 3,
tags: <String>['organic', 'india'],
extra: <String, String>{'sort': 'price', 'page_size': '40'},
);
print('Empty search: $empty');
print('Guest search: $guest');
print('Asha search: $asha');
// The same literal, with every optional field null, keeps only the defaults.
print('All null: ${search(q: null, page: null, tags: null)}');
} Output
Empty search: {format: json, page_size: 20}
Guest search: {format: json, page_size: 20, q: moong dal}
Asha search: {format: json, page_size: 40, sort: price, q: moong dal, page: 3, tags: organic,india}
All null: {format: json, page_size: 20}
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run query_params.dart
The four printed lines show which rules fired:
Empty searchhas only the two defaults, because every optional field was null and every collection if sawnull.Guest searchaddsq: 'moong dal'through the null-aware map entry'q': ?q. The entry copiesqonly when it is not null, so aString?value fits aMap<String, String>by exactly the field whose value is missing.Asha searchspreadsextrawith...?extra.extraholds'page_size': '40'and'sort': 'price', which land after the defaults, so the later value forpage_sizewins and the final map carries 40. The collection if forpageruns becausepageis 3, and thetagsbranch runs because the list has values; both line up with the specification’s “Insert the value into the surrounding collection”.- The last line passes
q: null, page: null, tags: nullon purpose. Every optional branch drops out, and the result is the empty-search output again. One literal, one shape, every missing input gracefully silent.
Inside a list or a set, a null-aware element is one character: ?maybeNumber adds maybeNumber only when it is not null, with its non-nullable type. The same three sources, written with and without the ?, make the difference visible:
// A null-aware element inside a list literal: ?value adds value only when it is not null.
// Compare the elements written with ? against the one written without it.
void main() {
int? absentValue = null;
int? presentValue = 3;
final items = [1, ?absentValue, ?presentValue, absentValue, 5];
print('items: $items');
print('length: ${items.length}');
// The same three sources in a set and in a map value.
final tags = <int>{1, ?absentValue, ?presentValue};
print('tags: $tags');
final row = <String, int>{'first': 1, 'second': ?presentValue, 'third': ?absentValue};
print('row: $row');
print('row has third: ${row.containsKey('third')}');
} Output
items: [1, 3, null, 5]
length: 4
tags: {1, 3}
row: {first: 1, second: 3}
row has third: false
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run null_aware_elements.dart
The list came out as [1, 3, null, 5] with a length of 4: ?absentValue added nothing, ?presentValue added 3, and only the fourth element, written without ?, kept its null. The map behaves the same way one level down: 'third': ?absentValue left the key out altogether, so containsKey('third') is false rather than a key whose value is null.
Spreading a nullable collection as if it were ordinary
List<int>? maybe; var items = [...maybe]; is a compile-time error, because maybe could be null. The fix is one character: write ...?maybe and maybe as null is treated as empty, so no element is inserted.
Spread also drops a common dead-end: linking the pieces into a bigger list with +. A spread of [...a, ...b] runs once and allocates one list; two + calls allocate two lists, the first a throwaway. The List class says + “returns the concatenation of this list and other”, so the extra list is unavoidable. For a lazy walk over several sources without building anything, the lazy Iterable API has followedBy and expand.
Key idea
Spread, null-aware spread and null-aware elements let one literal carry the shape of the collection and defer every optional piece until the data decides: no builder, no half-built list, no extra object.
Checkpoint
Check: Spread and null-aware spread
Const makes the literal a shared, deeply immutable object
A const collection is a compile-time constant: A value the compiler can build before the program runs, from literals, other constants, arithmetic on constant numbers and const constructors. Dart declares one with const, and no part of it can ever change. More in “Variables: var, final, const and type inference”: Dart builds it before the program runs and keeps it as a canonical constant: The single object Dart keeps for each constant value: two const expressions that build equal values give the same object, so identical() on them returns true. More in “Variables: var, final, const and type inference”, so two const expressions that build equal values share one object. This program makes three pairs of names and asks identical which of them point to the same list:
// const_identity.dart (Dart 3.13.5): two const literals that build equal
// values share one object, while two non-const literals do not. A const
// literal with spreads is still canonical when every piece is constant.
void main() {
const groceries1 = <String>['bread', 'milk', 'eggs'];
const groceries2 = <String>['bread', 'milk', 'eggs'];
final boughtOnMonday = const <String>['bread', 'milk', 'eggs'];
final boughtOnFriday = const <String>['bread', 'milk', 'eggs'];
final writtenOnMonday = <String>['bread', 'milk', 'eggs'];
final writtenOnFriday = <String>['bread', 'milk', 'eggs'];
print('const declaration twice: ${identical(groceries1, groceries2)}');
print('const literal in a final twice: '
'${identical(boughtOnMonday, boughtOnFriday)}');
print('plain literal twice: '
'${identical(writtenOnMonday, writtenOnFriday)}');
// All three hold equal items; only identity differs.
print('every list equals the first: '
'${groceries1.toString() == writtenOnMonday.toString()}');
// A const literal built from other const literals with a spread is also
// canonical: two const spreads of the same const base share one object.
const base = <int>[1, 2, 3];
const withFour = <int>[...base, 4];
const withFourAgain = <int>[...base, 4];
print('const spread of const base: '
'${identical(withFour, withFourAgain)}');
} Output
const declaration twice: true const literal in a final twice: true plain literal twice: false every list equals the first: true const spread of const base: true
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run const_identity.dart
Its five lines are three answers and one proof. Two const declarations with the same shape share one object, and so do two const [...] expressions written in final holders; two plain literals do not. All three hold equal items, as the every list equals the first: true line shows, so the choice is between identical, which compares reference identity, and ==, which compares contents. Walk through what Dart actually keeps:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- Two const names, one object. const groceries1 and const groceries2 both hold the same list literal. The compiler keeps one canonical object for each const value, so both names point to it and identical() returns true.
- const inside final: the same. boughtOnMonday and boughtOnFriday store the result of two const [...] expressions. The expression is a constant too, so canonicalisation finds the same object as the one above the names already know.
- Plain literals: two objects. writtenOnMonday and writtenOnFriday hold ordinary [...] literals, each built every time the line runs. The two lists hold equal items, but they are different objects, so identical() is false even though == for the items would be true.
- Deeply immutable: safe to share. Dart may share const lists only because they are deeply immutable: a const list, every item inside it and the items of any nested const list all refuse change at run time. Two names can hold one object without one path quietly changing what the other reads.
A const collection is also deep immutability: The guarantee that nothing inside a value ever changes, however many layers deep. A const collection in Dart is deeply immutable: its length, its elements and any const collections inside them all refuse change at run time.: nothing inside it ever changes, which is the only reason sharing one object is safe. Any attempt to grow, shrink or replace an element throws at run time, with Dart’s own words for what the thing is:
// const_fails.dart (Dart 3.13.5): a const list is deeply immutable. Reading
// is fine; the smallest change throws at run time, as this program shows.
void main() {
const groceries = <String>['bread', 'milk', 'eggs'];
// Reading is fine.
print('Length: ${groceries.length}, first: ${groceries.first}');
// Any attempt to change it throws. The error text names what Dart stores
// the const list as, an unmodifiable list.
groceries.add('rice');
} Output (exit status 255)
Length: 3, first: bread
Printed as an error (standard error)
Unhandled exception: Unsupported operation: Cannot add to an unmodifiable list #0 UnmodifiableListMixin.add (dart:_internal/list.dart:112:5) #1 main (const_fails.dart:11:13) #2 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:313:19) #3 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run const_fails.dart
The last three lines of its output end with Unsupported operation: Cannot add to an unmodifiable list. Reading the first element, counting the length and iterating still work; only writes are refused.
A const literal is also strict about what sits inside. Every item has to be constant too (a literal number, a string, another constant, or a const constructor call), and a non-constant inside is a compile-time error (the message reads Error: Not a constant expression.). A spread inside a const works only when its source is also constant, as the withFour and withFourAgain lines of the first program show.
When to reach for const
Use const for a set of allowed values a program checks against (a list of currency codes, a map of error messages), the default state of a page or widget, and compile-time tables such as a lookup from a code to a label. The compiler keeps one copy, the compiler enforces that nothing changes, and tests do not have to defend against someone reassigning it.
For a cache key, a widget key, or a value tested with identical across the program, the sharing is the point. Two parts of the program that build the same key return one object; the look-up finds the same slot every time.
Key idea
const collections are canonical and deeply immutable: equal const literals share one object, every item inside must be constant, and the smallest write throws at run time.
Checkpoint
Check: Const collections and canonical identity
A bare literal picks its type from context, or from you
Dart’s static analyser picks the element type of a literal from how it is used. Written on their own, [] and {} have no information to work with, so a bare [] is a List<dynamic> and a bare {} is a map with dynamic keys and values. The moment the literal has to fit a target, inference uses the target. This program asks each literal what it became:
// Where a collection literal's element type comes from: the context, an explicit type
// argument, or the items themselves. runtimeType prints what Dart settled on.
void main() {
List<String> cities = [];
final empty = <String>[];
final numbers = [1, 2, 3];
final mixed = <num>[1, 1.5, 2];
Map<String, int> scores = {};
final tags = <String>{};
print('List<String> cities = [] -> ${cities.runtimeType}');
print('final empty = <String>[] -> ${empty.runtimeType}');
print('final numbers = [1, 2, 3] -> ${numbers.runtimeType}');
print('final mixed = <num>[…] -> ${mixed.runtimeType}');
print('Map<String, int> scores={} -> ${scores.runtimeType}');
print('final tags = <String>{} -> ${tags.runtimeType}');
// No context at all: the literal falls back to dynamic.
final bare = [];
final braces = {};
print('final bare = [] -> ${bare.runtimeType}');
print('final braces = {} -> ${braces.runtimeType}');
// A spread takes the element type of its source, not of the items it happens to hold.
final names = <String>['Asha', 'Bala'];
final objects = <Object>['Asha', 'Bala'];
print('[...names] -> ${[...names].runtimeType}');
print('[...objects] -> ${[...objects].runtimeType}');
} Output
List<String> cities = [] -> List<String>
final empty = <String>[] -> List<String>
final numbers = [1, 2, 3] -> List<int>
final mixed = <num>[…] -> List<num>
Map<String, int> scores={} -> _Map<String, int>
final tags = <String>{} -> _Set<String>
final bare = [] -> List<dynamic>
final braces = {} -> _Map<dynamic, dynamic>
[...names] -> List<String>
[...objects] -> List<Object>
Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run infer_types.dart
Read the right-hand column: a target type (List<String> cities = []) and an explicit type argument (<String>[]) both give List<String>, items alone give List<int>, and a literal with no context at all gives List<dynamic> or a dynamic map. The two map and set lines print _Map and _Set rather than Map and Set, because runtimeType names the class the SDK instantiated; the type arguments beside it are what inference settled on.
Three cases need a nudge:
- A bare
[]where the context does not say what the element type is: the right fix is<String>[]or whichever element type you mean, so later code is checked against it. Returning[]from a function whose return type isList<String>does what you expect; a top-levelfinal empty = [];does not. - An empty literal with braces:
{}is aMap, not aSet. A set of strings is<String>{}. - A literal built mostly through a spread:
[...names]takes its element type fromnames, so a spread ofList<String>givesList<String>without any angle brackets. A spread ofList<Object>givesList<Object>, even when every item is a string, so the type of the source matters.
A List is also covariant, which the lists lesson covers in its sort and copy section: a List<Object> can hold a List<int>, which is fine for reading and surprises a line that writes into it. For a literal that will be read and written, write the element type you mean on the literal itself; the lint always_specify_types can enforce it.
Checkpoint
Check: Element-type inference of a literal
Practise: a breadcrumbs builder and a query-string helper
In a new console project, add the two functions below. Build each result in one literal: treat add, addAll, insert and remove on lists and maps as off limits, so the literal has to carry every decision.
List<String> breadcrumbs({required String track, String? module, String? lesson, bool showHome = true})returns the trail of names a learner sees above a lesson title.showHome: trueadds'Home'first; the track name always follows; the module and the lesson join the trail only when they are not null, in that order. Think of a reader on a mobile that falls back to a lighter page: the function should work when every field buttrackis null.Map<String, String> queryParams({String? q, int? page, List<String>? tags})returns the parameters a search request should carry. Each field appears only when it has a value;tagsbecomes a comma-separated string. The result is always aMap<String, String>, so the request builder can read it straight into a URL.
Make a console project with dart create -t console literals_lab and check each function against the happy case and
one empty case: all-null input, an empty tags list, and the Hindi menu of this lesson’s own figure.
Then work through the lesson’s self-check; the idea you know least comes first:
Practice that adapts to you
Practise this lesson
9 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): When should you reach for spread rather than add in a list literal?
Reach for spread when the extra items are already in a collection and should land in the result in order. [...head, ...tail] builds one list in one pass: the compiler sees the shape of the final list, inference picks the element type from the two sources, and no half-built list exists in between. add and addAll work on a list that is already there, so they ask you to allocate the outer list first, then change it, which means the result cannot be const, cannot be returned straight from an expression and cannot be the right-hand side of a => short-form function. If one of the sources might be null, use ...?maybe instead of a guard clause; it is one character, and the surrounding literal stays an expression you can read from top to bottom. Keep add for a loop that decides as it goes what joins the result, where no literal shape fits.
The module’s bank has two design questions that build on this section:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A function buildQuery({String? q, int? page, List<String>? tags, Map<String, String>? extra}) returns a Map<String, String>. A teammate's version has four if statements and a mutable local map. Rewrite it as one literal, say what each feature does, and name the check the literal still cannot carry.
-
Mid to senior
A unit test asserts identical(buildPalette(), buildPalette()), where buildPalette returns a List<Color> of eight colours. The test fails on every run, although the two lists hold equal colours. A teammate suggests making the list final; a second suggests marking it const. Which is right, and what other changes do you have to make?
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What are collection if and collection for?
Elements of a list, set or map literal, not statements: one literal can turn parts on and off, or repeat them, without a builder.
-
Why does [if (signedIn) menu.add('Settings')] not compile?
add changes a list in place and returns void, so the literal tries to insert a void. Write the element itself: if (signedIn) 'Settings'.
-
What do spread, null-aware spread and null-aware elements do?
... inserts every value of a source; ...? treats a null source as empty; a null-aware element, since Dart 3.8, drops a null value or a null-valued map entry without a guard.
-
Why is [...maybe] a compile error when maybe has a nullable list type?
maybe could be null, and a plain spread needs a collection. ...?maybe treats null as empty and inserts nothing.
-
What makes a const collection different from an ordinary one?
It is canonical and deeply immutable: identical on two matching const literals is true, every item inside must be a constant, and the smallest write throws at run time.
-
What types do a bare [] and a bare {} get with no context?
List<dynamic> and Map<dynamic, dynamic>. Write the element type, such as <String>[] or <String>{}, when no context gives one.
-
Does a literal with spreads reuse the collections it spreads?
No. It always builds a new collection; the lazy Iterable methods walk several sources without building a list for the walk.
Key takeaways
- Collection
ifand collectionforare elements of a literal, so one list, set or map literal can turn parts on and off without a builder. - Spread inserts every value of a source; null-aware spread treats a null source as empty; null-aware elements, since Dart 3.8, drop a null value or a null-valued map entry without a guard.
- A
constcollection is a canonical and deeply immutable value:identicalon two matching const literals is true, every item inside must be a constant and the smallest write throws at run time. - A bare
[]is aList<dynamic>and a bare{}is aMap<dynamic, dynamic>; write the element type (<String>[],<String>{}) when the context does not give one. - A literal with spreads always builds a new collection; the lazy Iterable API walks several sources without allocating a list for the walk.
Terms in this lesson
- collection for
- A for element inside a Dart list, set or map literal, with the shape for (var x in xs) e or a C-style for. It walks the loop and adds the element its body builds to the surrounding collection, zero or more times.
- collection if
- An if element inside a Dart list, set or map literal. It writes zero or one element into the surrounding collection and may carry an else branch, so one literal can turn a piece on or off from the data.
- deep immutability
- The guarantee that nothing inside a value ever changes, however many layers deep. A const collection in Dart is deeply immutable: its length, its elements and any const collections inside them all refuse change at run time.
- null-aware element
- A ? before an element or a map key or value, added in Dart 3.8. The element or entry is inserted only when its expression is not null, so a nullable value fits a non-nullable element type in one character.
- null-aware spread
- A ...? followed by a nullable collection, inside a Dart list, set or map literal. A null source is treated as empty: no element is inserted, and no guard is needed before the spread.
- spread element
- An ... followed by an Iterable, written inside a Dart list, set or map literal. It inserts every value of the source into the surrounding collection, in order, so one literal can build on another.
References
- Language tour: Collections (The Dart project (Google))
- Language tour: Variables, final and const (The Dart project (Google))
- Dart SDK CHANGELOG (null-aware elements in 3.8.0) (The Dart project (Google))
- List class (The Dart project (Google))
- identical function (The Dart project (Google))
- Dart language specification (The Dart project (Google))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress