Learn how `reduce` combines a collection into one result, how to choose the right accumulator shape, and how it differs from loops and stream reduction in Java.
reduce is the function you reach for when many inputs must become one result.
That result might be:
For Java developers, the safest mental model is:
reducereplaces the loop that keeps updating one accumulator.
reduce Doesreduce repeatedly combines:
until one final result remains.
1(reduce + 0 [1 2 3 4])
2;; => 10
The reducing function must know how to take:
and return the new accumulator value.
In Java, this is often written as a loop that mutates a local variable.
In Clojure, the accumulator is still there, but it is explicit in the function call rather than hidden in mutable control flow.
| Java shape | Clojure shape |
|---|---|
total += amount inside a loop |
reduce with a reducing function and accumulator |
mutable Map updated over time |
reduce returning a new map at each step |
stream .reduce(...) |
reduce over seq-able collections |
The conceptual improvement is that the aggregation rule becomes a named function contract rather than scattered loop mechanics.
One of the most important design choices in reduce is the initial accumulator.
1(reduce + 0 amounts)
That 0 is not decoration. It tells the reader:
The same idea applies to other accumulator shapes:
| Goal | Initial value |
|---|---|
| Sum numbers | 0 or 0M |
| Build a vector | [] |
| Build a map | {} |
| Build a set | #{} |
Choosing the initial value well makes the reduction easier to understand and safer on empty collections.
Suppose you want to count orders by status.
1(defn count-by-status [orders]
2 (reduce (fn [counts {:order/keys [status]}]
3 (update counts status (fnil inc 0)))
4 {}
5 orders))
This is a good reduce example because the whole job is “walk the collection and keep updating one summary value.”
The accumulator here is a map, not a number. That is the key lesson:
reduce is about aggregation, not just arithmetic.
1(defn orders-by-id [orders]
2 (reduce (fn [acc order]
3 (assoc acc (:order/id order) order))
4 {}
5 orders))
This is often easier to reason about than an imperative loop that mutates a temporary map.
reduce Is The Wrong ToolDo not use reduce just because it is powerful.
| If the job is… | Prefer |
|---|---|
| change every item | map |
| keep only matching items | filter |
| combine many items into one result | reduce |
Java developers sometimes overuse reduce because it looks “more general.” That is true, but not always helpful. If a map or filter says the job more directly, use the clearer tool.
A reducing function should answer one question:
how does the next item update the accumulator?
1(defn add-billable-hours [total entry]
2 (if (:time-entry/billable? entry)
3 (+ total (:time-entry/hours entry))
4 total))
5
6(reduce add-billable-hours 0 entries)
That separation helps because:
| Mistake | Why it hurts | Better move |
|---|---|---|
| Omitting the initial value carelessly | Empty collections or accumulator type become less clear | Supply an explicit initial value when clarity matters |
Using reduce for simple transformation |
The code hides a one-to-one mapping behind aggregation machinery | Use map |
| Building an overly complicated accumulator | Reviewers must mentally simulate too much state | Split the problem or reduce into a simpler shape |
Treating reduce as inherently more efficient or advanced |
Generality is not the same as clarity | Choose the function that best matches the job |
reduce And Pipeline DesignMany readable pipelines end with reduce:
1(->> orders
2 (filter :order/paid?)
3 (map :order/total)
4 (reduce + 0M))
That reads naturally:
This is often clearer than trying to force all three ideas into one large reducing function.