Recursion with `recur`

Learn how `recur` makes specific recursive shapes stack-safe in Clojure, and why it is not the same thing as general automatic tail-call optimization.

recur: A special form that performs a local jump back to the nearest loop or function arity when used in tail position, making that recursion stack-safe.

One of the most important Clojure recursion rules is also one of the most misunderstood: Clojure does not provide general automatic tail-call optimization for arbitrary recursive calls. Instead, it provides recur, which is explicit, local, and predictable.

What recur Actually Does

recur does not call a function the ordinary way. It rebinds the local recursion point with new values and jumps back without growing the stack.

That makes it ideal for:

  • loops over numeric counters
  • accumulator-style processing
  • sequence walks that carry explicit state
1(defn factorial [n]
2  (loop [n n
3         acc 1]
4    (if (zero? n)
5      acc
6      (recur (dec n) (* acc n)))))

This is stack-safe because recur is in tail position and targets the loop.

Tail Position Is Non-Negotiable

recur must be the final action in its branch. This fails conceptually:

1;; not valid use of recur
2(if (zero? n)
3  acc
4  (+ 1 (recur (dec n) acc)))

The addition happens after the recursive step, so the recursive jump is not in tail position. Clojure rejects this because it cannot safely turn it into a stack-neutral jump.

That restriction is a feature. It forces you to be explicit about the shape of the recursion.

loop and recur Belong Together

The most common pattern is loop/recur, which gives you iterative control flow while staying inside functional local bindings.

1(defn sum-xs [xs]
2  (loop [remaining xs
3         total 0]
4    (if (empty? remaining)
5      total
6      (recur (rest remaining)
7             (+ total (first remaining))))))

This is often clearer than using raw Java-style looping constructs because the state transitions stay explicit and immutable at each step.

recur Can Also Target the Current Function Arity

You do not always need an explicit loop. A function can recur to its own current arity:

1(defn countdown [n]
2  (if (zero? n)
3    :done
4    (recur (dec n))))

This works because the recursion point is the function arity itself. But once the state gets richer, loop often reads more clearly because the state variables are grouped visibly at the top.

Use Accumulators to Make Recursive Work Tail-Safe

Many recursive problems become recur-friendly only after introducing an accumulator.

1(defn reverse-xs [xs]
2  (loop [remaining xs
3         acc '()]
4    (if (empty? remaining)
5      acc
6      (recur (rest remaining)
7             (cons (first remaining) acc)))))

This is a standard technique: move deferred work into explicit state so the recursive jump can be the last step.

recur Is Not for Mutual Recursion

Because recur only targets the nearest local recursion point, it does not solve mutual recursion between separate functions. If two functions call each other, recur cannot jump between them.

That means you should not think of recur as universal recursion optimization. It is a local control-flow construct with strict rules.

    flowchart TD
	    A["Recursive problem"] --> B{"Can the next step be the final action?"}
	    B -- Yes --> C["Use `recur` with explicit state"]
	    B -- No --> D["Refactor with accumulators or another approach"]
	    C --> E["Stack-safe local recursion"]

Prefer Simpler Sequence Functions When They Already Express the Job

Not every loop should become manual recur. If the goal is already well captured by:

  • map
  • filter
  • reduce
  • transduce
  • some

then those are often better than handwritten recursion. recur is most helpful when the state transition is specific enough that the higher-level helper would hide more than it clarifies.

Common Mistakes

  • assuming Clojure has general automatic tail-call optimization
  • using recur outside tail position
  • reaching for manual recursion when reduce or another sequence helper already fits
  • writing recursive code without explicit accumulator state when the problem really needs it
  • trying to use recur for mutual recursion between different functions

Key Takeaways

  • recur gives explicit local tail recursion, not global automatic TCO.
  • It must be in tail position.
  • loop/recur is the most common stack-safe iteration pattern.
  • Accumulators are often the key to making recursion recur-friendly.
  • Use higher-level sequence functions when they already express the job clearly.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026