DEV Community

Shaishav Patel
Shaishav Patel

Posted on

JavaScript reduce Explained — How to Read Any One-Liner in 30 Seconds

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);
Enter fullscreen mode Exit fullscreen mode

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 — what accumulator starts 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
Enter fullscreen mode Exit fullscreen mode

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"}] }
Enter fullscreen mode Exit fullscreen mode

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 what acc starts as)

The inner line is dense:

(acc[item[key]] = acc[item[key]] || []).push(item);
Enter fullscreen mode Exit fullscreen mode

Translated to plain English:

  1. Look up acc[item[key]] — for example, acc["admin"] on the first iteration.
  2. If it doesn't exist yet (first time we see "admin"), initialize it to [].
  3. Push the current item into that array.
  4. 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 }
Enter fullscreen mode Exit fullscreen mode

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!
}, []);
Enter fullscreen mode Exit fullscreen mode

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 countingarray.length, or a simple for loop, often reads more clearly to people skimming the code
  • When the accumulator is the same shape as the input — you probably want map or filter instead

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);
Enter fullscreen mode Exit fullscreen mode
// 2
"hello world".split("").reduce((acc, ch) =>
  ({...acc, [ch]: (acc[ch] || 0) + 1}), {});
Enter fullscreen mode Exit fullscreen mode
// 3
[[1, 2], [3, 4], [5, 6]].reduce((acc, sub) => acc.concat(sub), []);
Enter fullscreen mode Exit fullscreen mode

Spoilers (with the pattern walkthrough):

  1. Accumulator = running sum; transform = if current number is positive, add it, else skip. Result: sum of positives only = 1+3+5 = 9.
  2. 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}.
  3. Accumulator = flattened array; transform = concat the current sub-array onto it. Result: [1, 2, 3, 4, 5, 6]. (This is what arr.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


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)

Collapse
 
dannyengelman profile image
Danny Engelman

Minor note, your groupBy example should use Object.groupBy()

MDN: Object.groupBy