Adding amounts up, counting occurrences, grouping rows by country: three different operations sharing one shape, an array going in and a single value coming out.
reduce is the method describing that shape, and the most feared of the family because it asks you to name two things instead of one.
Definition
reduce walks an Array while carrying one value from pass to pass, the accumulator. On each item the function receives that accumulator and the item, and returns the new accumulator. The last one returned is the result.
const amounts = [12, 30, 8];
const total = amounts.reduce((sum, a) => sum + a, 0);
console.log(total); // 50The trailing 0 is the accumulator's starting value, often taken for a detail. It actually decides the type of the result: 0 for a number, "" for a string, {} for an object, [] for an array.
The initial value is not optional
It can be left out, and the first item then serves as the accumulator. On an empty array, there is nothing left to take.
try {
[].reduce((a, b) => a + b);
} catch (err) {
console.log(err.name); // TypeError
console.log(err.message); // Reduce of empty array with no initial value
}
console.log([].reduce((a, b) => a + b, 0)); // 0An empty array is no rare case: it is what a Array.filter() hands back when it found nothing, or a query with no results. Writing the initial value costs three characters and removes the failure.
Grouping data
This is where reduce clearly beats a loop, because the output structure is declared at the end of the line.
const sales = [
{ country: "FR", amount: 40 },
{ country: "BE", amount: 15 },
{ country: "FR", amount: 60 },
];
const byCountry = sales.reduce((acc, s) => {
acc[s.country] = (acc[s.country] ?? 0) + s.amount;
return acc;
}, {});
console.log(byCountry); // { FR: 100, BE: 15 }Do not rebuild the accumulator on every pass with Spread (...), as in reduce((acc, s) => ({ ...acc, [s.country]: s.amount }), {}). The cost becomes proportional to the square of the item count, and the line hardly reads better. Change the accumulator, then return it.
Frequently asked questions
When is a plain loop the better choice?
As soon as the function runs past five or six lines, or has to stop early. reduce always walks the whole array, with no way out. A for loop with a break then says the same thing far more plainly.
Can reduce replace Array.map() and filter?
Technically yes, in a single pass. In practice the speed gain is small and the code reads markedly worse: the name map announces a transformation, a reduce announces nothing. Keep it for what the others cannot do.
What is reduceRight for?
For walking the array from the end to the start, with the same signature. The real case is function composition, where the order of application matters. For a sum or a count, the direction changes nothing in the result.