tezvyn:

Dart's Lazy Iterable Methods: map() and where()

AI-drafted, machine-checkedSource: api.dart.devintermediate

Think of `map()` or `where()` not as instant transformations, but as recipes for a new list. They're lazy, only doing work when you iterate. Use them to chain operations without creating intermediate lists.

WHY IT EXISTS To provide a memory-efficient way to transform and filter collections. Creating a new list for every single operation (e.g., filtering a list, then mapping the results) would be wasteful, especially with large datasets. Lazy iterables avoid this by deferring computation until the result is actually needed.

THE MENTAL MODEL An Iterable returned by map() or where() is like a blueprint or a recipe, not the finished cake. It knows the original ingredients (the source iterable) and the instructions (your function). The "baking" (computation) only happens slice by slice, as you ask for each element. If you ask for the whole cake again later, it bakes it all over again from scratch.

HOW IT WORKS When you call myList.where((e) => e > 10), Dart doesn't loop through myList. It creates a new Iterable object that holds a reference to myList and your function (e) => e > 10. When you later iterate this new Iterable (e.g., with a for-in loop or .toList()), its internal iterator pulls an element from myList, runs your function on it, and only yields the element if it passes the test. This happens on-demand for each element. The same applies to map().

WHEN TO USE IT Use these methods for chaining multiple transformations on a collection. For example, filtering a large list of users, then mapping their emails to a new list. This is the primary use case where laziness provides significant performance and memory benefits by not creating intermediate collections for each step. It's also great for creating potentially infinite sequences with Iterable.generate.

WHEN NOT TO USE IT Don't use lazy iterables if you need to access the results multiple times and the computation is expensive. In that case, it's better to pay the upfront cost of creating a List once by calling .toList() at the end of your chain. This materializes the result into memory, so subsequent accesses are fast and don't re-run the original transformations. Also, avoid functions with side effects (like logging or network calls) inside map or where, as their execution can be unpredictable.

ONE CANONICAL EXAMPLE Consider a list of 1,000,000 numbers. If you run numbers.where((n) => n.isEven).map((n) => n * 2), no computation happens yet. If you then ask for .first, it will iterate numbers, find the first even number (2), map it to 4, and return it. Only one element was processed. If you call .toList(), it will then iterate the entire list, filter all 500,000 even numbers, and map them, creating a final list in memory. The lazy approach avoided creating an intermediate list of 500,000 numbers.

Read the original → api.dart.dev

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.