~ / track D / fp foundations

Lazy evaluation

Intermediate

Lazy evaluation defers work until its result is actually needed. In Clojure this shows up most visibly in the lazy seq: a sequence whose elements are not produced until a consumer asks. You can describe an infinite stream of values, transform it, and only materialize the prefix you care about.

Minimal example

range with no upper bound is an infinite lazy seq. Nothing is computed yet:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

A take decides how much of it to realize:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

Transformations like map and filter are themselves lazy — they don't process their input until consumed:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

You should see seen 0, seen 1, seen 2 printed — and nothing more. The infinite tail was never visited.

Building your own lazy seq

lazy-seq wraps an expression that yields the next chunk only on demand. Here we define the natural numbers from n by recursion that doesn't blow up because each step is delayed:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

The realization trap

Lazy seqs have one famous gotcha: side effects don't happen until something forces them. This bites people who use map for side effects without realizing the seq.

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate
loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

Rule of thumb: use map/filter for transformations whose output you want; use doseq/run! when you only care about the side effect.

Why this matters

  • Composable infinite pipelines. You write the recipe once; the consumer decides how much to compute.
  • Memory efficiency. Processing a million-line log line-by-line keeps a small window in memory, not the whole file.
  • Predictability requires care. Mixing laziness with side effects can surprise you; learn to force realization explicitly when you need it.

Check yourself

? quiz

Why does `(do (map println [:a :b]) :done)` print nothing?

Exercise

Use iterate and take to produce the first 10 powers of 2 starting from 1 (so [1 2 4 8 16 32 64 128 256 512]). iterate builds a lazy seq by repeatedly applying a function.

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate
 status: new