Browse Learn Clojure Foundations as a Java Developer

How Tail Recursion Works in Clojure

Understand tail position, accumulator state, and why Clojure requires explicit recur instead of automatically optimizing ordinary recursive calls.

Java developers often learn recursion as a clean way to describe trees, parsers, and divide-and-conquer algorithms, but deep recursion on the JVM still risks stack overflow. In Clojure, tail recursion is useful only when it is expressed with recur, the explicit form that the compiler can turn into a stack-safe jump.

What is Tail Recursion?

Tail recursion is a recursive shape where the recursive step is the final action. In Clojure, the stack-safe version is not an ordinary self-call that happens to be last; it is an explicit recur to the nearest function or loop.

Tail Recursion vs. Regular Recursion

In regular recursion, each recursive call adds a new layer to the call stack, which can lead to stack overflow if the recursion is too deep. In Clojure, a tail-recursive shape becomes stack-safe only when the recursive step is written as recur.

Example of Regular Recursion in Java:

 1public class Factorial {
 2    public static int factorial(int n) {
 3        if (n == 0) {
 4            return 1;
 5        } else {
 6            return n * factorial(n - 1);
 7        }
 8    }
 9
10    public static void main(String[] args) {
11        System.out.println(factorial(5)); // Output: 120
12    }
13}

In this Java example, each call to factorial creates a new stack frame, which can lead to stack overflow for large values of n.

Example of Tail Recursion in Clojure:

1(defn factorial [n]
2  (letfn [(fact-helper [acc n]
3            (if (zero? n)
4              acc
5              (recur (* acc n) (dec n))))]
6    (fact-helper 1 n)))
7
8(println (factorial 5)) ; Output: 120

In this Clojure example, recur is the final operation in the helper function, so the compiler can reuse the current frame instead of growing the stack.

Understanding the recur Keyword

In Clojure, recur performs a tail-position jump. It can target the current function or the nearest loop; if it is not in a valid tail position, the compiler rejects the code.

How recur Works

  • Replaces the Function Call: Instead of calling the function again, recur reuses the current function’s stack frame.
  • Last Operation: recur must be the last operation in the function or loop.
  • Same Arity: The arguments passed to recur must match the function’s arity.

Example of Using recur in a Function:

1(defn sum [n]
2  (letfn [(sum-helper [acc n]
3            (if (zero? n)
4              acc
5              (recur (+ acc n) (dec n))))]
6    (sum-helper 0 n)))
7
8(println (sum 5)) ; Output: 15

In this example, recur is used to call sum-helper with updated arguments, ensuring that the recursive call is the last operation.

Benefits of Tail Recursion

Tail recursion offers several benefits, especially in functional programming languages like Clojure:

  • Prevents Stack Overflow: By reusing the current stack frame, tail recursion prevents stack overflow errors.
  • Avoids Frame Churn: recur avoids creating a new stack frame for each step.
  • Simplifies Code: Tail-recursive functions can be easier to read and understand, as they often eliminate the need for additional state management.

Comparing Tail Recursion in Java and Clojure

Java does not natively support tail-call optimization, which can make deep recursion problematic. Clojure solves the common loop-shaped case with explicit recur, while ordinary recursive calls still grow the stack.

Java Example Without Tail Recursion:

 1public class Sum {
 2    public static int sum(int n) {
 3        return sumHelper(0, n);
 4    }
 5
 6    private static int sumHelper(int acc, int n) {
 7        if (n == 0) {
 8            return acc;
 9        } else {
10            return sumHelper(acc + n, n - 1);
11        }
12    }
13
14    public static void main(String[] args) {
15        System.out.println(sum(5)); // Output: 15
16    }
17}

In Java, each recursive call adds a new stack frame, which can lead to stack overflow for large values of n.

Clojure Example with Tail Recursion:

1(defn sum [n]
2  (letfn [(sum-helper [acc n]
3            (if (zero? n)
4              acc
5              (recur (+ acc n) (dec n))))]
6    (sum-helper 0 n)))
7
8(println (sum 5)) ; Output: 15

In Clojure, recur allows the function to run in constant stack space, preventing stack overflow.

Visualizing Tail Recursion

To better understand how tail recursion works, let’s visualize the flow of a tail-recursive function using a diagram.

    graph TD;
	    A[Start] --> B[Check Base Case];
	    B -->|Base Case True| C[Return Accumulator];
	    B -->|Base Case False| D[Update Accumulator];
	    D --> E[Decrement n];
	    E --> F[Recur with New Values];
	    F --> B;

Diagram Explanation: This flowchart illustrates the process of a tail-recursive function. The function checks the base case, updates the accumulator and n, and then uses recur to call itself with the new values.

Try It Yourself

Now that we’ve explored tail recursion in Clojure, let’s try modifying the code examples to deepen your understanding:

  1. Modify the Factorial Function: Change the base case to handle negative numbers by returning nil for negative inputs.
  2. Implement a Tail-Recursive Fibonacci Function: Use recur to implement a function that calculates the nth Fibonacci number.
  3. Experiment with Large Inputs: Test the tail-recursive functions with large inputs to see how they handle deep recursion.

Exercises

To reinforce your understanding of tail recursion, try solving the following exercises:

  1. Exercise 1: Implement a tail-recursive function to calculate the greatest common divisor (GCD) of two numbers.
  2. Exercise 2: Write a tail-recursive function to reverse a list.
  3. Exercise 3: Create a tail-recursive function to flatten a nested list.

Key Takeaways

  • Tail Recursion: A recursive shape where the next step is the last operation.
  • recur Keyword: Clojure’s explicit form for stack-safe tail-position jumps.
  • Stack Benefit: recur avoids one new stack frame per step.
  • Comparison with Java: Java developers usually write deep linear recursion as loops; Clojure often uses loop/recur or reduce.

For Java developers, tail recursion in Clojure is mainly a way to express loop-like state transitions without mutable loop variables.

Further Reading

For more information on tail recursion and functional programming in Clojure, check out the following resources:

Quiz: Tail Recursion in Clojure

### What is tail recursion? - [x] A form of recursion where the recursive call is the last operation in the function. - [ ] A form of recursion that uses multiple recursive calls. - [ ] A form of recursion that does not involve base cases. - [ ] A form of recursion that is only used in Java. > **Explanation:** Tail recursion is characterized by the recursive call being the last operation in the function, allowing for optimization. ### How does the `recur` keyword benefit tail recursion in Clojure? - [x] It allows the function to reuse the current stack frame. - [ ] It creates a new stack frame for each recursive call. - [ ] It eliminates the need for base cases. - [ ] It is only used for non-recursive functions. > **Explanation:** The `recur` keyword enables the function to reuse the current stack frame, preventing stack overflow. ### What is a key practical difference between Java recursion and Clojure's `recur`? - [x] Clojure has explicit `recur` for stack-safe tail-position jumps. - [ ] Java automatically rewrites recursive methods into loops. - [ ] Clojure automatically optimizes every recursive self-call. - [ ] Java forbids recursive methods. > **Explanation:** `recur` is explicit and limited; ordinary recursive calls in both languages still consume stack frames. ### Which of the following is a benefit of tail recursion? - [x] Prevents stack overflow. - [ ] Increases stack usage. - [ ] Requires more memory. - [ ] Slows down execution. > **Explanation:** Tail recursion prevents stack overflow by reusing the current stack frame. ### What must be true for a function to be tail-recursive? - [x] The recursive call must be the last operation. - [ ] The function must have multiple recursive calls. - [ ] The function must not have a base case. - [ ] The function must use a loop. > **Explanation:** For a function to be tail-recursive, the recursive call must be the last operation. ### In Clojure, what does the `recur` keyword do? - [x] It performs a tail-recursive call. - [ ] It creates a new function. - [ ] It ends the recursion. - [ ] It initializes a loop. > **Explanation:** The `recur` keyword is used to perform a tail-recursive call in Clojure. ### What is a common use case for tail recursion? - [x] Calculating factorials. - [ ] Creating new objects. - [ ] Managing threads. - [ ] Handling exceptions. > **Explanation:** Tail recursion is commonly used for calculating factorials and other recursive computations. ### How does `recur` improve stack behavior? - [x] By reusing the current frame for a valid tail-position jump. - [ ] By increasing memory usage. - [ ] By creating new stack frames. - [ ] By eliminating base cases. > **Explanation:** `recur` prevents per-step stack growth when the jump is in a valid tail position. ### True or False: Tail recursion can prevent stack overflow. - [x] True - [ ] False > **Explanation:** Tail recursion can prevent stack overflow by reusing the current stack frame. ### True or False: Java automatically makes tail-recursive methods stack-safe. - [ ] True - [x] False > **Explanation:** Java recursive calls still consume stack frames; Clojure provides explicit `recur` for the common loop-shaped case.
Revised on Saturday, May 23, 2026