skip to content

In Dart, how does a collection for element compare with map().toList() for building lists and maps, including filtering with a nested if?

level: middleimportance: should knowfreq 45%

answer

  1. a loop that yields elements
  2. for-in or C-style header
  3. nest if to filter
  4. {for (u in users) u.id: u}
  5. no toList inside a spread

basics

~20 s

A collection for element loops inside the literal and inserts one element per iteration; a nested if filters, fors nest, and fixed elements can surround it, replacing where().map().toList() chains and Map.fromIterable with one eager literal.

solid answer

~40 s

`[for (final item in items) Tile(item)]` evaluates the loop while the literal is built and inserts the body's element on each pass. The header is either `for (x in iterable)` or C-style `for (var i = 0; i < n; i++)`. The body is an element, not a statement, so it can be another `for`, an `if` that filters, or a spread, but not `break` or a local declaration. Compared with `items.where(...).map(...).toList()` it reads top-down, needs no closures, and mixes naturally with fixed elements such as a header. For maps, `{for (final u in users) u.id: u}` replaces `Map.fromIterable`, which the recommended `prefer_for_elements_to_map_fromIterable` lint flags. The result is built eagerly, in full, every time the literal runs.

code

dart · 16 lines
dart
class User {
  User(this.id, this.name, {this.isAdmin = false});
  final String id;
  final String name;
  final bool isAdmin;
}

void main() {
  final users = [User('u1', 'Ana', isAdmin: true), User('u2', 'Ben')];

  final byId = {for (final u in users) u.id: u};
  final adminNames = [for (final u in users) if (u.isAdmin) u.name];

  print(byId.keys); // (u1, u2)
  print(adminNames); // [Ana]
}

go deeper

for a junior

Know the shape [for (final x in xs) f(x)] and that an if inside it filters without leaving gaps.

for a middle

Explain that the body is an element rather than a statement, show the map form {for (u in users) u.id: u}, and compare it with method chains.

for a senior

Enforce the recommended lints in review, and recognise when a for element inside build should become ListView.builder because the list is large.

for a principal

Set conventions for when a data-driven literal is fine and when list construction belongs in a view model or a lazy builder, balancing readability against rebuild cost.

## What a collection for element is A **collection for element** is a loop placed inside a list, set or map literal. Each time the loop body runs, it contributes zero or more values to the collection being built. It arrived with collection `if` and spread in **Dart 2.3**. Two header forms are allowed, mirroring the `for` statement: - `for (var x in iterable) element` iterates an `Iterable`; - `for (var i = 0; i < n; i++) element` is the C-style form. For example, `[1, for (var x = 5; x > 2; x--) x, 7]` evaluates to `[1, 5, 4, 3, 7]`. ## The body is an element, not a statement This is the rule that shapes everything else. The body is **one element**, which may be: 1. an expression (`Text(item.label)`); 2. a map entry (`u.id: u`) inside a map literal; 3. an `if` element, which **filters**; 4. another `for` element, which **nests**; 5. a spread, which inserts a group per iteration. Because it is not a statement block, you cannot write `break`, `continue`, `return` or a local variable declaration in it. When the logic needs those, build the list in a helper function or use a `sync*` generator instead (lazy generators belong to the iterables topic). ## Filtering and nesting ```dart var numbers = [1, 2, 3, 4, 5, 6, 7]; var evens = [0, for (var n in numbers) if (n.isEven) n, 8]; // [0, 2, 4, 6, 8] ``` The `if` is evaluated for each iteration, so non-matching values are simply skipped; nothing is inserted in their place. Loops nest the same way list comprehensions do in other languages: ```dart var ys = [1, 2, 3, 4]; var pairs = [ for (var x = 0; x < 3; x++) for (var y in ys) if (x < y) x + y * 10, ]; // [10, 20, 30, 40, 21, 31, 41, 32, 42] ``` ## Compared with iterable method chains | Aspect | `for` element | `where().map().toList()` | |---|---|---| | Reads | top-down, like a loop | right-to-left through closures | | Mixing fixed elements | natural: `[header, for (...) tile, footer]` | needs a spread of the chain | | Maps | `{for (u in users) u.id: u}` | `Map.fromIterable` or `Map.fromEntries` | | Evaluation | eager, once per literal evaluation | lazy until `toList()` | Neither is wrong; Effective Dart shows the `for` element as the preferred way to combine fixed and computed items. Two lints in the `recommended` set enforce the common cases: - `prefer_for_elements_to_map_fromIterable` flags `Map.fromIterable` where a `for` element would do; - `unnecessary_to_list_in_spreads` flags `...items.map(f).toList()`, because the spread already iterates the `Iterable` and the `toList()` builds a throwaway list. ## Evaluation order and cost A `for` element runs **when the literal is evaluated**, completely, before the collection exists: - iterations run in source order, and values are appended in that order; - the iterated source is read once per evaluation of the literal; - the finished list is independent of the source, so later changes to `numbers` do not affect `evens`. This is the opposite of a `where(...).map(...)` chain, which returns a lazy `Iterable` that recomputes each time it is iterated until you call `toList()`. Inside a Flutter `build` method the literal is rebuilt on every build, which is fine for a menu of ten entries and wasteful for thousands of rows. ## In a Flutter menu A menu model filtered by role becomes one literal: ```dart ListView( children: [ const DrawerHeader(child: Text('Menu')), for (final entry in entries) if (!entry.adminOnly || isAdmin) ListTile(title: Text(entry.label), onTap: entry.onTap), ], ) ``` The same loop over a large or unbounded data set is a different problem: a `for` element creates **every** widget each build, whereas `ListView.builder` creates only the visible ones. Use the literal for short, bounded lists such as menus and form sections. ## Mistakes to avoid - Expecting a skipped iteration to leave a `null` in the list. - Trying to `break` out of a `for` element. - Writing map entries inside a list literal: `[for (u in users) u.id: u]` does not compile; the braces make it a map. - Keeping `.toList()` inside a spread.

  • Can a Dart collection for element produce more than one value per iteration?
    Yes: make its body a spread, for example `for (final section in sections) ...[Header(section), ...section.tiles]`. Each iteration then inserts the header followed by that section's tiles, all into the same flat list.
  • Why can a Dart for element not use break, and what do you do when you need it?
    Its body is an element, not a statement block, so statements like `break`, `continue` or variable declarations are not allowed. Move the logic into a helper that builds and returns the list, or iterate a bounded source such as `items.take(n)`.

saying these in an interview costs you the question

  • A for element whose if filter fails inserts null for that iteration
  • You can break out of a collection for element early like a normal loop
  • Map entries can be produced by a for element inside a list literal
  • A for element is lazy and only runs when the list is first read
  • Spreading map() requires toList() first or it will not compile