Browse Clojure Foundations for Java Developers

Aggregating Data with `reduce`

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:

  • a number
  • a map
  • a set
  • a vector
  • any other accumulated value

For Java developers, the safest mental model is:

reduce replaces the loop that keeps updating one accumulator.

What reduce Does

reduce repeatedly combines:

  • the current accumulator
  • the next item in the collection

until one final result remains.

1(reduce + 0 [1 2 3 4])
2;; => 10

The reducing function must know how to take:

  • the accumulated result so far
  • the next item

and return the new accumulator value.

Why It Feels Different From A Java Loop

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.

Start With The Initial Value

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:

  • what kind of result is being built
  • what the empty-case starting point is

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.

A Realistic Example: Totals by Status

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.

Another Example: Build A Lookup Map

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.

When reduce Is The Wrong Tool

Do 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 Good Reducing Function Has One Clear Job

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:

  • the accumulator shape stays obvious
  • the update rule is easy to test
  • the collection traversal stays out of the business logic

Common Early Mistakes

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 Design

Many readable pipelines end with reduce:

1(->> orders
2     (filter :order/paid?)
3     (map :order/total)
4     (reduce + 0M))

That reads naturally:

  • choose the relevant orders
  • extract the values you care about
  • combine them into one total

This is often clearer than trying to force all three ideas into one large reducing function.

Knowledge Check

### When is `reduce` the right tool? - [x] When many items need to be combined into one accumulated result - [ ] When every item should become a corresponding output item - [ ] When only some items should survive unchanged - [ ] When the collection only needs to be sorted > **Explanation:** `reduce` is for aggregation. It combines a collection into one result by repeatedly updating an accumulator. ### Why is the initial accumulator value important? - [x] It communicates the result shape and provides a clear starting value, especially for empty collections - [ ] It makes `reduce` behave like `map` - [ ] It forces the reducing function to be lazy - [ ] It removes the need for a reducing function > **Explanation:** The initial value is part of the meaning of the reduction. It shows what kind of result is being built and how the process begins. ### In `count-by-status`, what kind of value is the accumulator? - [x] A map being updated as each order is processed - [ ] A vector of pending orders - [ ] A boolean flag - [ ] A Java stream > **Explanation:** The result being built is a summary map from status to count, so the accumulator must have that same shape. ### Why can `reduce` be a poor choice for a simple one-to-one transformation? - [x] It hides a mapping problem inside heavier aggregation machinery - [ ] It only works on numeric collections - [ ] It cannot return collections - [ ] It always mutates the original collection > **Explanation:** `reduce` is more general, but if the job is simply "transform each item," `map` usually communicates the intent much more directly.
Revised on Friday, April 24, 2026