Iterables and collection methods

Dart · Essentials · 10 min

Most data shaping in Dart and Flutter is a chain of iterable methods, and laziness explains surprising behavior.

List and Set are Iterables, as are map.keys, map.values and map.entries. Iterable offers a rich set of methods that take functions:

  • map(f) — transform each element.
  • where(test) — keep elements matching a condition; whereType<T>() filters by type.
  • expand(f) — map each element to several and flatten.
  • fold(initial, combine) and reduce(combine) — combine into one value.
  • any, every, contains, firstWhere(test, orElse: ...).
  • take, skip, takeWhile, skipWhile.
  • join(', '), toList(), toSet().

Key fact: map, where, expand and friends are lazy. They return an Iterable view that runs the function each time it is iterated. If you iterate twice, the function runs twice; if you never iterate, it never runs. Call .toList() when you need a concrete, reusable result or a List type (Flutter's children requires a List<Widget>).

package:collection adds helpers such as firstWhereOrNull, groupBy, sortedBy and deep equality. In Dart 3 the core library also has firstOrNull, lastOrNull and indexed (pairs of index and element as records).

sort mutates the list in place and returns void, so it can't be chained; copy first if you need the original order.

Example

void main() {
  final words = ['dart', 'is', 'fun', 'and', 'fast'];

  final lengths = words.map((w) => w.length);   // lazy Iterable<int>
  print(lengths.toList());                      // [4, 2, 3, 3, 4]

  final total = lengths.fold(0, (sum, n) => sum + n);
  print(total);                                 // 16

  print(words.where((w) => w.startsWith('f')).join(' '));  // fun fast
  print(words.any((w) => w.length > 3));        // true
  print([[1, 2], [3]].expand((l) => l).toList());          // [1, 2, 3]

  var calls = 0;
  final upper = words.map((w) { calls++; return w.toUpperCase(); });
  upper.first; upper.first;
  print(calls);                                 // 2 (lazy, re-run)

  for (final (i, w) in words.take(2).indexed) {
    print('$i:$w');                             // 0:dart  1:is
  }
}

Remember

  • map/where are lazy views — call toList() to materialize
  • fold combines with an initial value; reduce needs a non-empty iterable
  • sort mutates in place and returns void

Watch out for

  • Passing items.map(...) where a List is required, or iterating an expensive lazy chain many times.

Check yourself

  1. `final it = [1, 2, 3].map(f);` — how many times has `f` run after this line alone?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dart.dev/libraries/collections/iterables