Browse Learn Clojure Foundations as a Java Developer

Java Loop Constructs and Clojure Equivalents

Review how Java for, enhanced for, while, and do-while loops express counting, traversal, accumulation, and conditional repetition, then map each intent to idiomatic Clojure forms.

Java gives you loop statements. Clojure gives you expressions that return values. That difference matters more than the syntax.

When you move Java loop code into Clojure, start by asking what the loop is doing:

Java loop intent Common Java construct Clojure first choice Use loop/recur when
Transform every item Enhanced for, stream map map, mapv, for comprehension You need custom stepping state that is awkward as a sequence pipeline
Keep selected items Enhanced for, stream filter filter, remove, keep The filter depends on several evolving local values
Accumulate one result for plus mutable accumulator reduce, transduce, into You need early termination or several accumulators
Count with an index Classic for loop map-indexed, keep-indexed, range You must update index and accumulator together
Repeat until a condition changes while, do-while iterate, take-while, some, loop/recur The next state depends on the previous state
Traverse a tree or nested value Recursion or explicit stack Direct recursion, tree-seq, zipper APIs The traversal is tail-recursive and local

The rest of this page translates the Java constructs you already know into the Clojure choice that best preserves intent.

Classic for Loops: Counted Work

A Java for loop combines initialization, condition checking, mutation, and body execution:

1int total = 0;
2for (int i = 1; i <= 5; i++) {
3    total += i;
4}

In Clojure, the first translation is usually not direct recursion. If the loop is reducing a range to one value, use reduce:

1(def total
2  (reduce + (range 1 6)))

If the index is meaningful, make it data:

1(def labels
2  (map-indexed (fn [idx value]
3                 {:index idx
4                  :value value})
5               ["alpha" "beta" "gamma"]))

That mental move is important for Java engineers: the counter is often not control flow anymore; it is an input to a pure transformation.

Enhanced for: Collection Traversal

An enhanced Java for loop hides the index but still often mutates an external result:

1List<String> activeNames = new ArrayList<>();
2
3for (User user : users) {
4    if (user.isActive()) {
5        activeNames.add(user.getName());
6    }
7}

The Clojure version should make the transformation visible:

1(def active-names
2  (->> users
3       (filter :active?)
4       (map :name)
5       vec))

The result is explicit. There is no external collection to mutate, and each step can be tested independently.

while: State Until a Condition Fails

Java while loops are common when the number of iterations is not known:

1int attempts = 0;
2while (!ready() && attempts < 3) {
3    refresh();
4    attempts++;
5}

In Clojure, use a sequence operation if you are consuming values. Use loop/recur when you are advancing local state:

1(defn wait-until-ready [ready? refresh!]
2  (loop [attempts 0]
3    (cond
4      (ready?) :ready
5      (= attempts 3) :timed-out
6      :else (do
7              (refresh!)
8              (recur (inc attempts))))))

recur is not automatic tail-call optimization. It is an explicit jump back to the nearest loop or function entry, and Clojure only allows it in tail position. That restriction is useful because it makes stack-safe loops visible in the code.

do-while: Execute Once, Then Decide

Java’s do-while guarantees that the body runs at least once:

1String token;
2do {
3    token = readToken();
4} while (token.isBlank());

Clojure normally models that as a small recursive loop with the first read inside the loop body:

1(require '[clojure.string :as str])
2
3(defn read-nonblank-token [read-token]
4  (loop []
5    (let [token (read-token)]
6      (if (str/blank? token)
7        (recur)
8        token))))

This is one of the cases where loop/recur is clearer than forcing the code into map or reduce.

Break and Continue

Java gives you break and continue. Clojure usually encodes those decisions as data-flow choices:

Java control habit Clojure alternative Example use
continue after a failed predicate filter, keep, when inside for Skip invalid records
break after finding a match some, first plus filter, reduced in reduce Stop at the first qualifying value
Loop body builds a collection mapv, into, for, transducers Produce a new vector or map
Loop body updates multiple counters reduce with a map accumulator Count valid, invalid, and skipped rows

Early termination with reduce uses reduced:

1(defn first-large-amount [amounts]
2  (reduce (fn [_ amount]
3            (when (> amount 1000)
4              (reduced amount)))
5          nil
6          amounts))

Use this sparingly. If some expresses the search directly, prefer it.

Choosing the Clojure Shape

    flowchart TD
	    A["Java loop"] --> B{"One result?"}
	    B -->|Yes| C["reduce or transduce"]
	    B -->|No| D{"New collection?"}
	    D -->|Yes| E["map, filter, keep, for, into"]
	    D -->|No| F{"Local evolving state?"}
	    F -->|Yes| G["loop/recur"]
	    F -->|No| H{"Nested recursive shape?"}
	    H -->|Yes| I["direct recursion or tree-seq"]
	    H -->|No| J["restate the problem as data transformation"]

Practice

  1. Rewrite a Java loop that builds a list into a Clojure ->> pipeline.
  2. Rewrite a Java loop that sums values into reduce.
  3. Rewrite a Java while retry loop with loop/recur.
  4. Identify one loop in your Java codebase that should remain imperative in Java but become a sequence transformation in Clojure.

Key Takeaways

  • Java loops mix control flow, mutation, and result construction in one block.
  • Clojure separates those concerns with sequence functions, reducers, explicit loop/recur, and direct recursion for nested shapes.
  • Do not translate every Java loop into direct recursion. Translate the loop’s intent first.
  • Use recur when you need an explicit local loop and can keep the recursive jump in tail position.

Quiz: Java Loops and Clojure Equivalents

### What is usually the first Clojure choice for a Java loop that accumulates one result? - [x] `reduce` - [ ] Direct self-recursion - [ ] A mutable global variable - [ ] A Java-style `while` loop > **Explanation:** A loop that accumulates one result maps naturally to `reduce` because the changing accumulator becomes an explicit value. ### What does `recur` do in Clojure? - [x] It jumps to the nearest function or `loop` entry from tail position. - [ ] It optimizes every recursive call automatically. - [ ] It creates a lazy sequence. - [ ] It mutates the current local bindings. > **Explanation:** `recur` is explicit and only valid in tail position. It is not general automatic tail-call optimization. ### Which Clojure form best matches a Java enhanced `for` loop that keeps only valid records? - [x] `filter` - [ ] `Thread.sleep` - [ ] `def` - [ ] `case` > **Explanation:** Filtering is a data transformation, so a predicate-based sequence operation usually expresses the intent better than an explicit loop. ### When is `loop/recur` a good Clojure replacement for Java `while`? - [x] When local state must advance until a condition is met. - [ ] Whenever a collection is transformed item by item. - [ ] Whenever the Java code used an enhanced `for` loop. - [ ] Whenever the result is a constant. > **Explanation:** `loop/recur` is useful for explicit local state machines, retry loops, and similar control-flow-heavy cases. ### True or False: Clojure's `for` is the same kind of statement as Java's `for`. - [ ] True - [x] False > **Explanation:** Clojure's `for` is a sequence comprehension that returns values. Java's `for` is a statement for executing a body repeatedly.
Revised on Saturday, May 23, 2026