And now, the most powerful (and most confusing) method of all: reduce. It takes a whole array and reduces it down to a single value. That value could be a total, a maximum, a count, a string, an object... anything.
Here's the classic example, adding up numbers:
const numbers = [5, 10, 15, 20];
const total = numbers.reduce((runningTotal, n) => runningTotal + n, 0);
console.log(total);
It prints
50
reduce takes two arguments:
- A callback that receives the accumulator (the running result so far) and the current item, and returns the new accumulator.
- A starting value for the accumulator (here,
0).
This is exactly the accumulator pattern from Grade 5, packed into a method. Let's trace it step by step:
| Step | Accumulator (before) | Current item | Callback returns |
|---|---|---|---|
| 1 | 0 (the starting value) | 5 | 0 + 5 = 5 |
| 2 | 5 | 10 | 5 + 10 = 15 |
| 3 | 15 | 15 | 15 + 15 = 30 |
| 4 | 30 | 20 | 30 + 20 = 50 |
After the last item, the final accumulator (50) is the result.
Picture a snowball rolling down a hill. It starts small (the starting value). Each time it rolls over a patch of snow (an item), it picks some up and gets bigger (the callback decides how). At the bottom of the hill, you've got one big snowball (the result).
More reduce recipes
Finding the biggest:
const temps = [28, 35, 31, 29];
const hottest = temps.reduce((max, t) => (t > max ? t : max), temps[0]);
console.log(hottest);
It prints
35
Totaling a property:
const cart = [
{ item: "rice", price: 120, qty: 1 },
{ item: "eggs", price: 2, qty: 12 },
{ item: "milk", price: 15, qty: 2 },
];
const total = cart.reduce((sum, line) => sum + line.price * line.qty, 0);
console.log(`Cart total: $${total}`);
It prints
Cart total: $174
Counting things into an object:
const votes = ["burger", "omelet", "burger", "pasta", "burger", "omelet"];
const tally = votes.reduce((counts, vote) => {
counts[vote] = (counts[vote] || 0) + 1;
return counts;
}, {});
console.log(tally);
It prints
{ burger: 3, omelet: 2, pasta: 1 }Here the accumulator starts as an empty object {}. For each vote, we add one to that food's count. (The counts[vote] || 0 part means "use the current count, or 0 if there isn't one yet." Remember: undefined is falsy, so || falls back to 0.) The fried rice has won. Democracy has spoken.
You're technically allowed to leave out the starting value, in which case reduce uses the first item. But if the array is empty, it crashes:
const empty = [];
const total = empty.reduce((sum, n) => sum + n);
It stops with an error
Uncaught TypeError: Reduce of empty array with no initial value
With a starting value, [].reduce((sum, n) => sum + n, 0) safely gives 0. Always include one!
reduce can do almost anything, which makes it tempting to use everywhere. But for a lot of tasks, map, filter or a simple loop is much easier to read. Use reduce when you're truly boiling a list down into one thing, like a total or a tally.