What does .lazy do, and what does it cost?

2.1.10 · L2 · Interview

Predict first

In what order does this print?

let r = (1...5).lazy
    .map { n -> Int in print("map", n); return n * 2 }
    .filter { $0 > 4 }
print("built")
print(r.first!)
  • built, map 1, map 2, map 3, 6
  • built, map 1, map 2, map 3, map 3, 6
  • map 1 ... map 5, built, 6

Predict first

What does this print?

var calls = 0
let l = [1, 2, 3].lazy.map { n -> Int in calls += 1; return n }
_ = Array(l)
_ = Array(l)
print(calls)
  • 3
  • 6

Mental model. Eager work is cooking the whole banquet before anyone sits down. Lazy work cooks each dish when a guest asks for it.

.lazy returns a wrapper whose map, filter and friends do not run immediately. They record the pipeline and compute elements on demand, one at a time, with no intermediate arrays. That is a big win when you only need the first match, a prefix, or when the source is huge or infinite.

The cost is that nothing is cached. Every traversal runs your closures again, and a lazy filter has to run its predicate to answer count. Use lazy when you short-circuit; use a plain eager chain when you consume everything and read the result more than once.

Nothing runs while building. first scans until the filter passes (maps 1, 2, 3), then reads that element again to return it, so map 3 runs twice. Lazy never caches, not even inside one call.

A lazy sequence caches nothing. Two traversals run the closure on all three elements twice.

Common traps

  • Reading a lazy result twice. The closures run again each time, including any side effects.
  • Expecting .lazy to be faster everywhere. If you consume every element once, you save the intermediate arrays and little else.

They might follow up with

  • What does first(where:) gain from lazy? It stops at the first match, so later elements are never transformed.
  • Does lazy change the result type? Yes, to LazyMapSequence and friends. Wrap in Array(...) to materialise.

Related questions

  • 2.1.7 map, filter, compactMap, flatMap, reduce, forEach: when do you use each, and what bites?
  • 2.1.9 Sequence, Collection, BidirectionalCollection, RandomAccessCollection: what does each add?

← Sequence, Collection, BidirectionalCollection…   How do you write your own Collection, and what do you get for free? →