Skip to content
AgileLogics
5 min read

Iterables and Generators

Prerequisites: Collection methods, Classes and objects

An Iterable is anything you can loop over with for-in. Lists and sets are iterables, and so are the results of map and where.

Lazy iterables

Iterables returned by map, where, take, and friends are lazy: they do no work until you use them, and they stop as soon as they have enough:

void main() {
  final result = [1, 2, 3, 4]
      .map((n) {
        print('map $n');
        return n * 2;
      })
      .where((n) {
        print('where $n');
        return n > 2;
      })
      .first;
  print(result);
}
map 1
where 2
map 2
where 4
4

Only two elements were processed. Calling .toList() would have processed all four.

Generators: sync*

A function marked sync* returns an iterable, producing values one at a time with yield:

Iterable<int> fibonacci() sync* {
  var (a, b) = (0, 1);
  while (true) {
    yield a;
    (a, b) = (b, a + b);
  }
}

Iterable<int> countdown(int from) sync* {
  for (var i = from; i >= 1; i--) {
    yield i;
  }
}

void main() {
  print(fibonacci().take(10).toList());
  print(countdown(3).toList());
}
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[3, 2, 1]

fibonacci contains an infinite loop, but the program doesn't hang: values are only computed as they're requested.

Sorting by a key

class Order {
  final String customer;
  final double total;
  Order(this.customer, this.total);
}

void main() {
  final orders = [Order('Linus', 45), Order('Ada', 120), Order('Grace', 300)];

  orders.sort((a, b) => b.total.compareTo(a.total));
  print(orders.map((o) => o.customer).toList());

  final byName = [...orders]..sort((a, b) => a.customer.compareTo(b.customer));
  print(byName.map((o) => o.customer).toList());
}
[Grace, Ada, Linus]
[Ada, Grace, Linus]

sort changes the list in place; [...orders]..sort(...) sorts a copy. The popular package:collection adds helpers such as groupBy, sortedBy, and firstWhereOrNull.

Common mistakes

  • Iterating a lazy iterable several times, repeating the work each time. Call toList() once if you'll use the result repeatedly.