Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~24 min· topic 5 of 10

Topic 5.5

reduce

In one line

reduce walks through an array carrying one running value (the accumulator) and combines each item into it, turning a whole list into a single result such as a total, a count, a maximum or a grouped object; always give it a starting value.

Think of it like this

A piggy bank. You start with an empty piggy bank (the starting value, 0). Each day you drop in that day's coins (each item). The piggy bank carries what's inside from one day to the next (the accumulator). At the end of the month, you have one thing: the total. reduce does that: many items in, one value out.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

reduce
A method that combines all items of an array into one value, step by step.
Accumulator
The running result that reduce carries from one item to the next.
Initial value
The starting accumulator, the second argument to reduce, like 0 for a sum.
Reducer
The function you give reduce: it takes the accumulator and one item and returns the new accumulator.
Group
Sort items into buckets by some key, for example people by city.
Object.groupBy
An ES2024 built-in that groups array items into an object of arrays in one call.

Step by step

01Write the reducer

A reducer takes "the result so far" and "one more item" and returns the new result so far.

app.jswhole filejavascript
const add = (acc, n) => acc + n;
const total = [5, 10, 15].reduce(add, 0);
console.log(total); // 30

02Follow the accumulator

Each step's return value becomes the next step's acc.

Follow the accumulatordiagram
Rendering diagram…

03Count things

Start with an empty object. Each item adds one to its own counter. ?? 0 gives 0 the first time a key is seen.

app.jswhole filejavascript
const counts = ["a", "b", "a"].reduce((acc, letter) => {
  acc[letter] = (acc[letter] ?? 0) + 1;
  return acc;
}, {});
// { a: 2, b: 1 }

04Group things

Same idea, but each key holds an array. Or let Object.groupBy do it.

app.jswhole filejavascript
const byCity = Object.groupBy(people, (p) => p.city);
// { Pune: [ ... ], Delhi: [ ... ] }

05Always pass the initial value

Without it, an empty array throws, and arrays of objects start with an object as the accumulator.

app.jswhole filejavascript
[].reduce((a, b) => a + b);    // TypeError
[].reduce((a, b) => a + b, 0); // 0

Try it yourself

  1. 1

    Find the longest word

    Use reduce on ["hi", "hello", "hey"] to return the longest word. What initial value makes sense? Predict, then run.

  2. 2

    Average

    In the first example, compute the average of nums from total. Then predict what happens with an empty array (dividing by zero length).

  3. 3

    Forget the return

    In the count example, delete return acc;. Predict the error before you run it, then read the message.

Code & diagrams

Watch the accumulator grow New tab

The initial value sets the starting point and the type: 0 for a sum, 1 for a product, a string for joining text.

Console

(nothing was printed)

Expected console output

acc = 0 n = 5
acc = 5 n = 10
acc = 15 n = 15
total: 30
product: 24
I learn every day
Count and group ES2024+ New tab

`??` is ES2020, `??=` is ES2021 and `Object.groupBy` is ES2024. `Object.groupBy` keeps the whole objects in each group; the hand-written reducer kept only names.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

{ tea: 3, coffee: 2, juice: 1 }
{ Pune: [ 'Asha', 'Meera' ], Delhi: [ 'Ravi' ] }
[ 'Pune', 'Delhi' ] 2
Initial value pitfalls New tab

Without an initial value, the first object became the accumulator and `+` turned it into the text `[object Object]`. The last line throws on purpose.

Console

(nothing was printed)

Expected console output

6
0
9
[object Object]20
30
Uncaught TypeError: Reduce of empty array with no initial value

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

No initial value on an array that can be empty

Compute a cart total with cart.reduce((sum, n) => sum + n) when the cart is empty.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Reduce of empty array with no initial value

Break #2

Forgetting to return the accumulator

Write the counting reducer as (acc, v) => { acc[v] = (acc[v] ?? 0) + 1; } without return acc;.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot read properties of undefined (reading 'coffee')

Myth vs fact

Myth

reduce is only for adding numbers.

Fact

The accumulator can be any type: objects for counts and groups, arrays, strings, even a promise chain. The initial value picks the type.

Myth

The initial value is optional, so you can skip it.

Fact

It's technically optional, but skipping it breaks on empty arrays and on arrays of objects. Always pass it unless you're sure the array is non-empty and the items are the same type as the result.

Myth

Using reduce is always the most advanced, best way.

Fact

A reducer full of branches is often harder to read than a for...of loop. Pick the clearest tool; named methods like Object.groupBy or Math.max(...arr) say more.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Building a new object every step ((acc, x) => ({ ...acc, [x.id]: x })) copies the whole accumulator each time, making the reduce O(n²). Mutating the accumulator you created yourself (acc[x.id] = x; return acc;) is O(n) and still pure from the outside.

  • ▸

    Object.groupBy returns an object with a null prototype (no inherited toString and friends), so keys like "constructor" are safe. Map.groupBy returns a Map, which lets keys be objects or any type.

  • ▸

    reduce skips holes. Without an initial value, the first present element is used, so [, , 5].reduce(f) returns 5 without calling f at all.

  • ▸

    reduce is the same idea as fold in other languages. Interview tasks like "implement map with reduce" test that you understand the accumulator: arr.reduce((acc, x) => { acc.push(f(x)); return acc; }, []).

Remember this

  1. 1

    arr.reduce(fn, initialValue) calls fn(accumulator, element, index, array) for each element. The accumulator (often called acc) is the running result: on the first call it is initialValue; on every later call it is whatever your function returned last time. After the last element, reduce returns the final accumulator. The classic example is a sum: [5, 10, 15].reduce((acc, n) => acc + n, 0) goes 0 → 5 → 15 → 30.

  2. 2

    The result can be any type: a number (a total), a string, an object (counts per word, items grouped by city), even another array. The initial value decides the type, so choose it on purpose: 0 for sums, 1 for products, {} for counting or grouping, [] for building a list.

  3. 3

    Initial value pitfalls. If you leave out initialValue, reduce uses the first element as the starting accumulator and begins at the second one. That works for summing plain numbers, but goes wrong in two ways: on an empty array it throws TypeError: Reduce of empty array with no initial value, and when the items are objects, the first accumulator is an object, so acc + item.price produces text like "[object Object]20". Rule: always pass the initial value.

  4. 4

    Counting with reduce: start with {}, and for each item increase acc[item] by one: acc[item] = (acc[item] ?? 0) + 1; return acc;. Grouping: start with {}, and push each item into the list for its key. Since ES2024, Object.groupBy(arr, fn) and Map.groupBy(arr, fn) do grouping for you in one call and are clearer when available.

  5. 5

    Your callback must return the accumulator every time. Forgetting the return (easy with { } arrow bodies) makes the accumulator undefined on the next step. reduceRight does the same as reduce but walks from the last element to the first.

  6. 6

    reduce is powerful, but not always the clearest tool. A sum or a count reads well as reduce. A long reducer with ifs and several fields is often clearer as a plain for...of loop. Many tasks have a named method that says more: Math.max(...arr) for a maximum, filter(...).length for counting matches, Object.groupBy for grouping, flat() for flattening.

Explain it without notes

01

Explain how reduce works step by step for a sum.

02

What happens when you call reduce without an initial value?

03

How would you count how many times each word appears in an array?

04

When is a for...of loop better than reduce?

Practice

01

Use reduce to find the total price of [{ name: "Pen", price: 20 }, { name: "Bag", price: 900 }].

02

Use reduce to turn [["a", 1], ["b", 2]] into { a: 1, b: 2 }.

03

Group [1, 2, 3, 4, 5] into { odd: [...], even: [...] } with Object.groupBy.

Trade-offs

  • ↔

    reduce expresses "many into one" in a single expression; a loop is often easier to read and debug when the logic grows.

  • ↔

    Object.groupBy is the clearest way to group, but needs a 2024-or-later browser; a hand-written reducer works everywhere.

Done when you can

  • I can explain what the accumulator is and how it changes.

  • I can sum, multiply and join with reduce and the right initial value.

  • I can count and group items into an object.

  • I always pass an initial value and always return the accumulator.

  • I can choose between reduce, a loop and Object.groupBy.