Choose between direct recursion, sequence pipelines, reduce, loop/recur, lazy sequences, and Java interop by matching the Clojure construct to the shape of the work.
Java makes loops the obvious default. Clojure gives you several iteration shapes, and direct recursion is only one of them.
Use recursion when the problem is naturally recursive: trees, nested maps, parsers, dependency graphs, route trees, and traversals where the same structure appears inside itself. For most everyday work over a flat collection, start with sequence functions, reduce, or loop/recur.
| Work shape | Clojure starting point | Why |
|---|---|---|
| Transform every item | map, mapv, for, into |
The result shape follows the input collection |
| Keep or discard items | filter, remove, keep |
The predicate is the main idea |
| Accumulate one result | reduce, transduce |
The accumulator is explicit and stack-safe |
| Advance local state until done | loop/recur |
The next state depends on the previous state |
| Consume a large or infinite stream | lazy sequences, transducers | Work can be pulled as needed |
| Traverse nested self-similar data | direct recursion, tree-seq, explicit stack |
The code mirrors the data shape |
flowchart TD
A["Need repeated work"] --> B{"Flat collection?"}
B -->|Yes| C{"One result?"}
C -->|Yes| D["reduce or transduce"]
C -->|No| E["map, filter, keep, into"]
B -->|No| F{"Self-similar nested data?"}
F -->|Yes| G["direct recursion or traversal helper"]
F -->|No| H{"State machine or retry loop?"}
H -->|Yes| I["loop/recur"]
H -->|No| J["restate as data flow or isolate interop"]
Do not treat recursion as a mandatory replacement for every Java for loop. Clojure’s idiom is closer to “make the data transformation explicit” than “write a function that calls itself.”
Two details matter:
recur is explicit and compiler-checked. It jumps back to the nearest function or loop entry only from tail position.Java mental model: recursion is not the new default loop. It is the right tool when the problem shape repeats inside itself.