A lazy list, or lazy sequence, produces elements as a consumer requests them rather than computing the entire result up front. This makes large and even infinite sequences useful when only a finite prefix is needed. Defining `(range 1e20)` returns immediately; asking for elements starts the work. section 3.4.2
The main contract is about when work happens and how much is requested:
- Generators such as `range`, `iterate`, and `repeatedly` can supply elements incrementally. Bound an infinite result with a consumer such as `take`. section 3.4.2 section 9.2.1
- `map` transforms elements; `filter` and `remove` select them. Their collection-taking forms return lazy sequences, so avoid relying on exactly-once side effects in mapping functions or predicates. section 3.5.2 section 3.5.3
- An empty result is possible. `(seq [])` returns `nil`, whereas `(sequence [])` returns an empty sequence; `lazy-seq` itself does not return `nil`. section 9.1.1 section 9.3.1
Transform only what is needed
A pipeline can describe an unbounded computation while requesting a small result: `(take 5 (filter odd? (map inc (range))))` `;; (1 3 5 7 9)` `range` supplies numbers, `map` increments them, and `filter` retains odd results. `take` limits the requested output; no complete infinite result is required. section 3.4.2 section 3.5.2 section 3.5.3
To write a lazy producer, put the recursive step inside `lazy-seq` and join each produced value to the remainder with `cons`. The wrapper delays evaluation and caches its result once realized. Its body must ultimately yield something sequential: `(lazy-seq 1 2 3)` throws an `IllegalArgumentException` because its last expression is not a sequence. section 9.3.1
(defn eager-map [f coll] ; #1
(when-first [x coll]
(println "iteration")
(cons (f x)
(eager-map f (rest coll)))))
(def eager-out (eager-map str '(0 1 2))) ; #2
;; iteration
;; iteration
;; iteration
(defn lazy-map [f coll] ; #3
(lazy-seq
(when-first [x coll]
(println "iteration")
(cons (f x)
(lazy-map f (rest coll))))))
(def lazy-out (lazy-map str '(0 1 2))) ; #4
(first lazy-out) ; #5
;; "iteration"
;; "0"
Compare the eager and lazy versions of `map`: constructing the eager result prints every iteration, while the lazy version waits until `first` requests an element. section 9.3.1
Laziness does not make full traversal free. Counting a general lazy sequence walks and realizes it, usually taking linear time; a counted `long-range` is a notable exception. Holding on to a sequence’s head while consuming its tail can also retain realized elements and exhaust memory. Deeply nested lazy steps can overflow the stack. section 8.1.2 section 3.4.2 section 9.3.1
Incremental output does not guarantee an incremental input. `re-seq` can return just the first few matches, but its source string must already be in memory. Likewise, a lazy database result must be consumed before its connection closes, and streaming benefits depend on the driver. Use laziness to limit requested work, then check what the producer retains or requires. section 9.3.5 section 9.3.7

