Browse Learn Clojure Foundations as a Java Developer

loop/recur Examples in Clojure

Practice loop/recur with counters, accumulators, collection scans, and state transitions so Java loop patterns translate into clear Clojure code.

Java loop patterns usually contain a condition, one or more changing values, and a final result. loop/recur makes those pieces explicit in Clojure: the loop binding vector names the current state, and the recur call supplies the next state.

Understanding loop/recur

Before we dive into examples, let’s briefly understand how loop/recur works. The loop construct establishes a recursion point, and recur jumps back to that point from a valid tail position. That jump reuses the current frame instead of consuming stack space with each iteration.

Here’s a simple example to illustrate the syntax:

1(loop [i 0]
2  (when (< i 10)
3    (println i)
4    (recur (inc i))))

In this example, loop initializes a local binding i with the value 0. The when condition checks if i is less than 10, and if true, it prints i and calls recur with the incremented value of i. This process repeats until i reaches 10.

Iterative Algorithms with loop/recur

Let’s explore some iterative algorithms using loop/recur. We’ll start with a simple example and gradually move to more complex scenarios.

Example 1: Calculating Factorial

Calculating the factorial of a number is a classic example of recursion. However, it can also be implemented iteratively using loop/recur.

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

Explanation:

  • We define a function factorial that takes a single argument n.
  • The loop initializes two bindings: acc (accumulator) with 1 and i with n.
  • If i is zero, we return acc, which holds the factorial result.
  • Otherwise, we multiply acc by i and decrement i, then call recur.

Example 2: Fibonacci Sequence

The Fibonacci sequence is another classic example. Let’s implement it using loop/recur.

1(defn fibonacci [n]
2  (loop [a 0
3         b 1
4         i n]
5    (if (zero? i)
6      a
7      (recur b (+ a b) (dec i)))))
8
9(println (fibonacci 10)) ; Output: 55

Explanation:

  • We define a function fibonacci that takes n as an argument.
  • The loop initializes a with 0, b with 1, and i with n.
  • If i is zero, we return a, which holds the nth Fibonacci number.
  • Otherwise, we update a to b, b to a + b, and decrement i, then call recur.

Processing Collections with loop/recur

Clojure’s loop/recur can also be used to process collections. Let’s see how we can sum the elements of a vector.

Example 3: Summing a Vector

1(defn sum-vector [v]
2  (loop [acc 0
3         coll v]
4    (if (empty? coll)
5      acc
6      (recur (+ acc (first coll)) (rest coll)))))
7
8(println (sum-vector [1 2 3 4 5])) ; Output: 15

Explanation:

  • We define a function sum-vector that takes a vector v.
  • The loop initializes acc with 0 and coll with v.
  • If coll is empty, we return acc, which holds the sum.
  • Otherwise, we add the first element of coll to acc and call recur with the rest of coll.

Simulating State Changes Over Time

loop/recur can be used to simulate state changes over time, such as in a simple game or simulation.

Example 4: Simulating a Counter

Let’s simulate a counter that increments every second until it reaches a specified limit.

1(defn simulate-counter [limit]
2  (loop [count 0]
3    (when (< count limit)
4      (println "Count:" count)
5      (Thread/sleep 1000) ; Sleep for 1 second
6      (recur (inc count)))))
7
8(simulate-counter 5)

Explanation:

  • We define a function simulate-counter that takes a limit.
  • The loop initializes count with 0.
  • If count is less than limit, we print the count, sleep for 1 second, and call recur with the incremented count.

Comparing with Java

In Java, similar iterative logic would typically be implemented using for or while loops. Here’s how the factorial example might look in Java:

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

Comparison:

  • In Java, we use a for loop to iterate from n down to 1, multiplying acc by i in each iteration.
  • In Clojure, loop/recur achieves the same result with a more functional approach, emphasizing immutability and recursion.

Try It Yourself

Now that we’ve explored some examples, try modifying the code to deepen your understanding:

  • Factorial: Modify the factorial function to handle negative numbers gracefully.
  • Fibonacci: Change the fibonacci function to return a sequence of Fibonacci numbers up to n.
  • Sum Vector: Implement a function that multiplies all elements of a vector using loop/recur.
  • Simulate Counter: Adjust the simulate-counter function to decrement the counter instead of incrementing it.

Diagrams and Visualizations

To help visualize the flow of loop/recur, consider the following diagram illustrating the flow of a simple loop:

    graph TD;
	    A[Start] --> B[Initialize Variables];
	    B --> C{Condition Met?};
	    C -->|Yes| D[Perform Action];
	    D --> E[Update Variables];
	    E --> C;
	    C -->|No| F[End];

Diagram Description: This flowchart represents the iterative process of a loop/recur construct in Clojure. It starts by initializing variables, checks a condition, performs an action if the condition is met, updates variables, and repeats until the condition is no longer met.

Exercises and Practice Problems

  1. Implement a Prime Checker: Write a function using loop/recur that checks if a number is prime.
  2. Reverse a List: Use loop/recur to reverse a list without using built-in functions.
  3. Simulate a Simple Game: Create a simple game simulation where a character moves across a grid, using loop/recur to update the character’s position.

Key Takeaways

  • loop/recur provides an explicit way to perform custom iteration in Clojure.
  • Stack Safety: recur reuses the current frame when it is in tail position.
  • Comparison with Java: Java loops mutate local variables; Clojure’s loop/recur passes the next values as bindings.
  • Practical Applications: From iterative algorithms to state simulations, loop/recur is versatile and efficient.

Use loop/recur when the loop has custom state transitions. If the example can be expressed more clearly with map, filter, or reduce, prefer the higher-level function.

For further reading, explore the Official Clojure Documentation and ClojureDocs for more examples and detailed explanations.


Quiz: loop/recur Examples

### What is the primary advantage of using `loop/recur` in Clojure? - [x] It provides explicit stack-safe iteration. - [ ] It allows for mutable state within loops. - [ ] It is faster than traditional loops in Java. - [ ] It simplifies syntax compared to Java loops. > **Explanation:** `recur` reuses the current frame for valid tail-position jumps, so long iterative processes do not grow the stack. ### How does `loop/recur` differ from Java's `for` loop? - [x] `loop/recur` passes next values as bindings instead of mutating locals. - [ ] `loop/recur` allows for mutable variables. - [ ] `loop/recur` is only used for infinite loops. - [ ] `loop/recur` is a syntactic sugar for `for` loops. > **Explanation:** Java `for` loops commonly update local variables; `loop/recur` makes the next values explicit. ### In the `factorial` example, what does `recur` do? - [x] It calls the loop with updated arguments. - [ ] It exits the loop. - [ ] It initializes the loop variables. - [ ] It performs a side effect. > **Explanation:** `recur` is used to call the loop with updated arguments, effectively creating an iteration. ### What happens if the condition in a `loop/recur` is never met? - [x] The loop continues indefinitely. - [ ] The loop exits immediately. - [ ] An error is thrown. - [ ] The loop skips to the next iteration. > **Explanation:** If the condition is never met, the loop will continue indefinitely, similar to an infinite loop in Java. ### Which of the following is a valid use case for `loop/recur`? - [x] Iterative algorithms - [x] Processing collections - [ ] Directly modifying global state - [ ] Performing I/O operations > **Explanation:** `loop/recur` is ideal for iterative algorithms and processing collections, maintaining functional programming principles. ### How can you simulate state changes over time using `loop/recur`? - [x] By updating loop variables and using `recur`. - [ ] By using mutable variables. - [ ] By directly modifying global state. - [ ] By using Java's `Thread` class. > **Explanation:** State changes over time can be simulated by updating loop variables and using `recur` to iterate. ### What is a key difference between `loop/recur` and traditional recursion? - [x] `loop/recur` reuses the current frame for each valid recur jump. - [ ] `loop/recur` allows for mutable state. - [ ] `loop/recur` is faster than recursion. - [ ] `loop/recur` is only used for mathematical calculations. > **Explanation:** `recur` is the stack-safe jump back to the nearest function or loop. ### What is the purpose of the `loop` construct in Clojure? - [x] To establish a recursion point for `recur`. - [ ] To create mutable variables. - [ ] To perform side effects. - [ ] To simplify syntax. > **Explanation:** The `loop` construct establishes a recursion point for `recur`, allowing for iterative processes. ### Can `loop/recur` be used to process collections? - [x] Yes - [ ] No > **Explanation:** `loop/recur` can be effectively used to process collections, maintaining functional programming principles. ### True or False: `loop/recur` is only used for infinite loops. - [ ] True - [x] False > **Explanation:** `loop/recur` is not limited to infinite loops; it is used for custom iterative processes where explicit next values are clearer than a higher-level collection function.
Revised on Saturday, May 23, 2026