Lazy evaluation
IntermediateLazy 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:
A take decides how much of it to realize:
Transformations like map and filter are themselves lazy — they don't
process their input until consumed:
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:
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.
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.