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
rangeclojure.core/rangeLazy seq of integers; (range), (range n), or (range start end step).view on clojuredocs → with no upper bound is an infinite lazy seq. Nothing is computed yet:
A takeclojure.core/takeLazy seq of the first n items.view on clojuredocs → decides how much of it to realize:
Transformations like mapclojure.core/mapApply f to each element, returning a lazy seq.view on clojuredocs → and filterclojure.core/filterLazy seq of items where (pred x) is truthy.view on clojuredocs → 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 mapclojure.core/mapApply f to each element, returning a lazy seq.view on clojuredocs → for side effects without realizing the seq.
Rule of thumb: use mapclojure.core/mapApply f to each element, returning a lazy seq.view on clojuredocs →/filterclojure.core/filterLazy seq of items where (pred x) is truthy.view on clojuredocs → for transformations whose output you want;
use doseqclojure.core/doseqLike for, but for side effects; returns nil.view on clojuredocs →/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 iterateclojure.core/iterateLazy seq of x, (f x), (f (f x)), …view on clojuredocs → and takeclojure.core/takeLazy seq of the first n items.view on clojuredocs → to produce the first 10 powers of 2 starting from 1
(so [1 2 4 8 16 32 64 128 256 512]). iterateclojure.core/iterateLazy seq of x, (f x), (f (f x)), …view on clojuredocs → builds a lazy seq by
repeatedly applying a function.