reduce is the JavaScript array method that beginners avoid, intermediates misuse, and senior engineers use in unreadable one-liners. Once you can read it confidently, half of "advanced JavaScript" gets simpler — pipelines, group-by, frequency maps, flattens, builds, transforms. All of them are usually reduce in disguise.
This is a 30-second pattern for reading any reduce you encounter in someone else's code, with three real-world examples and a fast escape hatch when you don't have 30 seconds.
The Signature
Every reduce looks like this:
array.reduce((accumulator, currentItem) => {
// do something with accumulator and currentItem
return newAccumulator;
}, initialValue);
Four moving parts:
-
array— what you're iterating over -
accumulator— the "running total" that gets carried from one item to the next -
currentItem— the array element being processed right now -
initialValue— whataccumulatorstarts as before the first iteration
That's it. Every reduce you'll ever read is some combination of those four pieces.
The 30-second read is just: identify each of the four, then decide what the accumulator becomes by the end.
Example 1: Sum (the easy one)
const total = [1, 2, 3, 4].reduce((sum, n) => sum + n, 0);
// total === 10
Plugging into the template:
-
array=[1, 2, 3, 4] -
accumulator=sum -
currentItem=n -
initialValue=0
The function says: "take the running sum, add the current number, return the new sum." After all four items, sum is 10.
This is the classic example. If you understand only this one, you can read maybe 30% of reduces in the wild — the rest are just this with a more interesting accumulator shape.
Example 2: Group-By (the one most people get stuck on)
const groupBy = (arr, key) => arr.reduce((acc, item) => {
(acc[item[key]] = acc[item[key]] || []).push(item);
return acc;
}, {});
const users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "user" },
{ name: "Carol", role: "admin" },
];
groupBy(users, "role");
// { admin: [{name:"Alice"}, {name:"Carol"}], user: [{name:"Bob"}] }
Plugging into the template:
-
array=arr(the input) -
accumulator=acc(an object, the lookup map being built) -
currentItem=item(one object from the array) -
initialValue={}(empty object — that's whataccstarts as)
The inner line is dense:
(acc[item[key]] = acc[item[key]] || []).push(item);
Translated to plain English:
- Look up
acc[item[key]]— for example,acc["admin"]on the first iteration. - If it doesn't exist yet (first time we see "admin"), initialize it to
[]. - Push the current item into that array.
- The expression assigns the array back to
acc[item[key]]and returns the array, so.push(item)runs on the array we just created or fetched.
After all items, acc is the lookup map mapping each unique role to an array of users with that role.
This is the most common "what is this doing" reduce in real codebases.
Example 3: Frequency Map (one more pattern worth knowing)
const freq = ["a", "b", "a", "c", "a", "b"].reduce((acc, ch) => {
acc[ch] = (acc[ch] || 0) + 1;
return acc;
}, {});
// { a: 3, b: 2, c: 1 }
Plugging into the template:
-
accumulator=acc(an object, the count map) -
currentItem=ch(one character) -
initialValue={}
The inner line says: "look up the current count for this character (default to 0), add 1, store it back." Simple once you see it; opaque if you've never seen the (acc[ch] || 0) + 1 pattern.
Common Gotchas
A few traps that catch even experienced developers:
1. Forgetting the initial value. If you write arr.reduce((a, b) => a + b) without the 0, the first iteration uses arr[0] as the accumulator and arr[1] as the current item. For sums it still works, but for object-building reduces, the first element is treated as the accumulator and the code breaks weirdly.
Always pass an initial value unless you really know what you're doing.
2. Returning the wrong thing. The callback must return the new accumulator. If you write:
arr.reduce((acc, item) => {
acc.push(item); // pushed, but didn't return acc!
}, []);
The callback implicitly returns undefined, so the next iteration's acc is undefined and you get a crash like TypeError: Cannot read properties of undefined.
Always return acc at the end of the callback unless you're using an implicit return expression.
3. Mutating the accumulator vs returning a new one. Both work. Mutation (acc.push(item)) is faster and what most reduces use. Returning a new accumulator ({...acc, [key]: value}) is "more functional" but creates garbage. Most code in the wild mutates — don't get confused if the callback doesn't return a brand-new object.
When NOT to Use reduce
reduce is powerful, but it's not always the right tool. Two cases where something else is clearer:
-
Summing or counting —
array.length, or a simpleforloop, often reads more clearly to people skimming the code -
When the accumulator is the same shape as the input — you probably want
maporfilterinstead
A useful rule of thumb: if you can express the operation as "for each item, transform it" (map) or "for each item, keep some" (filter), use those. Reach for reduce when you're collapsing many items into a different shape (sum, group, lookup, build).
The Fast Escape Hatch
Sometimes you just need to know what the reduce does, right now, without thinking through it. If you're reading legacy code under deadline pressure, paste it into a free AI code explainer that returns a plain-English breakdown — switch to Explain mode, paste, click. You get back the language, the purpose in one sentence, a step-by-step bullet list, and what the inputs and outputs are.
That's faster than mentally tracing through a complex group-by reduce when you have other things to ship. Use it as the first read, then go back and read the code yourself once you have the high-level shape.
Practice: Read These Without Running Them
Try reading these reduces using the 30-second pattern (identify accumulator, current item, initial value, transformation):
// 1
[1, -2, 3, -4, 5].reduce((acc, n) => n > 0 ? acc + n : acc, 0);
// 2
"hello world".split("").reduce((acc, ch) =>
({...acc, [ch]: (acc[ch] || 0) + 1}), {});
// 3
[[1, 2], [3, 4], [5, 6]].reduce((acc, sub) => acc.concat(sub), []);
Spoilers (with the pattern walkthrough):
-
Accumulator = running sum; transform = if current number is positive, add it, else skip. Result: sum of positives only =
1+3+5 = 9. -
Accumulator = frequency map; transform = increment count for current character, returned as a new object (spread spread + override). Result:
{h:1, e:1, l:3, o:2, " ":1, w:1, r:1, d:1}. -
Accumulator = flattened array; transform = concat the current sub-array onto it. Result:
[1, 2, 3, 4, 5, 6]. (This is whatarr.flat()does, but written manually.)
If you got all three, you can read most reduces you encounter. If two of them tripped you up, re-skim the four-part signature and the three examples — those patterns cover most real-world cases.
Related Tools
- free AI code explainer that returns plain-English breakdown for any programming language — paste any reduce one-liner and get back the structured breakdown when you don't have 30 seconds to trace it
- free Markdown to HTML converter with live preview and syntax-highlighted code blocks — for when you're writing up a code-walk-through for your team's docs
- free online code beautifier for JavaScript, HTML, and CSS — for when the reduce is a minified one-liner with no whitespace, beautify first then read
The reason reduce confuses people is that it generalizes the "shrink many to one" pattern, and "one" can be any shape — a number, an object, an array, an even-more-complex nested structure. The trick to reading reduces fast isn't memorizing patterns. It's identifying the accumulator's shape, then watching how each iteration changes it. Once you have that habit, the syntax fades into the background and the actual transformation becomes obvious.
Try a free AI code explainer that breaks down any reduce in plain English →
Top comments (1)
Minor note, your groupBy example should use Object.groupBy()
MDN: Object.groupBy