jonnifyelixir
F3 / F3.04 / streams

F3.04 · part 3 of 3

Lazy streams

Where Enum runs each step to completion, Stream builds a recipe. Stream.map and Stream.filter compute nothing on their own; the work happens only when an Enum function pulls values through — and only as far as it needs.

That difference is invisible on five records and decisive on a learner's full history. If you want the first three completed lessons, an eager pipeline still scans everything; a lazy one stops the moment it has three.

Eager versus lazy

The same pipeline — keep completed lessons, take the first three — over a history of eight records. Toggle the strategy and watch how many records each one actually examines.

Strategy · select one

HISTORY · 8 RECORDS (green = completed)

      

Same result, different work. Stream pulls one element at a time and stops early; Enum finishes every step first. On small data it makes no difference — on large or expensive data, it is the difference.

When laziness wins

Reach for Stream when the source is large, when a step is expensive, when you only need part of the result — or when the source has no end at all. Build the recipe lazily, then run it once with an Enum call.

# lazy: nothing has run yet, only a recipe
stream =
  history
  |> Stream.map(&decode/1)
  |> Stream.filter(& &1.completed)

# runs now, and only as far as Enum.take needs
first_three = stream |> Enum.take(3)

# an endless source is fine when consumed lazily
Stream.iterate(1, & &1 * 2) |> Enum.take(5)  # => [1, 2, 4, 8, 16]

Enum · eager

Compute the whole collection now, every step to completion.

Stream · lazy

Compute on demand — only the elements that are actually pulled.

That completes F3.04 — the portal can read its collections eagerly or lazily. Next module: F3.05 — Structs, where the maps these functions walk gain a name and a shape. Return to the chapter overview.

References

Primary sources for this lesson, and where it connects in the course.

Sources

Related in this course