Array.prototype.reduce walks an array left to right, carrying a running value (the accumulator) from one element to the next, and returns that single final value. The one-line summary I give people: it folds a list of things down into one thing, and the thing it folds into can be a number, a string, an object, a Map, or another array.
const nums = [3, 1, 4, 1, 5];
const sum = nums.reduce((acc, current) => acc + current, 0);
// 14The callback gets (accumulator, current) on each step. Whatever you return becomes the accumulator for the next element. The 0 after the callback is the initial value, the seed the accumulator starts from. That seed is the part most reduce bugs come down to, so I am going to lead with it before anything else.
Always pass the initial value
This is the single most-hit reduce bug, and it is worth burning into muscle memory: if you call reduce with no initial value on an empty array, it throws.
[].reduce((a, b) => a + b);
// Uncaught TypeError: Reduce of empty array with no initial valueWhen you omit the seed, reduce uses the array's first element as the starting accumulator and begins iterating from index 1. On an empty array there is no first element to grab, so it cannot start, and it throws a TypeError rather than guessing. Pass the seed and the empty case just returns the seed:
[].reduce((a, b) => a + b, 0);
// 0, no throwThe seed is technically optional, but the only time omitting it is safe is when you can guarantee the array is never empty, and in real code you usually cannot make that promise (the data came from a fetch, a filter, a user). There is a second, quieter reason to always pass it: the seed pins the type of the accumulator. Sum into 0 and the accumulator is a number from step one. Sum into "" by accident and you are doing string concatenation. Build into {} or new Map() and the accumulator is the right shape on the first iteration instead of being whatever your first element happened to be. My rule is simple: every reduce gets an explicit second argument, no exceptions.
Summing numbers
The canonical example. No, there is no Math.sum for arrays, so reduce is the idiomatic one-liner:
const prices = [19.99, 4.5, 12];
const total = prices.reduce((acc, price) => acc + price, 0);
// 36.49If money is involved, remember these are IEEE-754 floats: 0.1 + 0.2 is not 0.3, and that error rides along through a reduce. Sum integer cents and divide at the end, or reach for a decimal library, the same as you would anywhere else in JavaScript. That is not a reduce problem, but reduce is where people first notice it.
Building an object or a Map
Reduce earns its keep when the result is not a scalar. A common shape is turning an array of records into a lookup keyed by id:
const users = [
{ id: 7, name: "Ada" },
{ id: 12, name: "Linus" },
];
const byId = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
// { 7: {id:7,name:"Ada"}, 12: {id:12,name:"Linus"} }The thing people forget here is the return acc. The callback has to hand the accumulator back every step. Mutate it, then return it. If you would rather not mutate, return a fresh object with spread ({ ...acc, [user.id]: user }), but that allocates a new object per element and is genuinely slower on large arrays, so for a plain build-up I mutate the accumulator and move on.
A Map is often the better target than a plain object: it keeps insertion order, takes any key type (not just strings), and has no prototype keys to collide with.
const byIdMap = users.reduce(
(acc, user) => acc.set(user.id, user),
new Map()
);Map.prototype.set returns the Map, so the callback body collapses to a single expression with no separate return line.
Where Object.groupBy now replaces the reduce-to-grouped-object pattern
For years the "group these items by a key" task was the textbook reduce, and you still see it everywhere:
const products = [
{ name: "cap", type: "hat" },
{ name: "beanie", type: "hat" },
{ name: "tee", type: "shirt" },
];
// The old reduce-to-grouped-object pattern:
const grouped = products.reduce((acc, p) => {
(acc[p.type] ??= []).push(p);
return acc;
}, {});As of Baseline 2024 you do not need reduce for this at all. Object.groupBy does exactly this, and reads as what it does:
const grouped = Object.groupBy(products, (p) => p.type);
// { hat: [ {cap}, {beanie} ], shirt: [ {tee} ] }It is supported across current browsers and in Node 21+ (it landed as a static method after an earlier Array.prototype.group proposal was renamed over web-compatibility issues, which is also a fine reason not to monkey-patch Array.prototype yourself). If you need a real Map instead of a null-prototype object (so non-string keys work), Map.groupBy is the sibling. For the wider tour of which method does what, see my JavaScript array methods guide. When grouping is all you are doing, group with the grouping tool.
Reach for map, filter, or groupBy first; reduce when you are genuinely folding
reduce is the most general array method, which is exactly why it is overused. Almost anything can be expressed as a reduce, but "can be" is not "should be." A reduce that only transforms each element is a map wearing a disguise; one that only keeps some elements is a filter; one that builds a grouped object is Object.groupBy. Each of those names tells the next reader what is happening at a glance, where a reduce makes them parse the callback to find out.
My heuristic:
- Transforming every element one-to-one, reach for
map. - Keeping a subset, reach for
filter. - Grouping by a key, reach for
Object.groupBy(orMap.groupBy). - Flattening one level, reach for
flat(orflatMapto map-and-flatten in one pass). - Collapsing the whole array into a single value whose computation needs the running result, that is a genuine fold, and that is reduce's job: a sum, a product, a min/max, a running balance, threading a value through a pipeline of functions.
That last category is where reduce is the clear, idiomatic choice and nothing else fits as cleanly. Everywhere else, the named method is easier to read and usually no slower. There is a small te-prefixed helper I keep around for the one fold I write most often, totalling a numeric field:
const teSumBy = (arr, key) => arr.reduce((acc, item) => acc + item[key], 0);
teSumBy([{ qty: 2 }, { qty: 5 }], "qty"); // 7It is a real fold (a running total that needs the accumulator), it seeds with 0, and it is named for what it does. That is the bar for reaching for reduce.
See also
- JavaScript array methods, explained: the full map, filter, find, flat, and groupBy tour, so you reach for the right one instead of forcing a reduce.
- JavaScript object methods:
Object.entries,fromEntries, andgroupBy, the toolkit for the object you just built with reduce. - Merge arrays in JavaScript: spread,
concat, andflat, the non-reduce ways to combine lists.
Sources
Authoritative references this article was fact-checked against.
- Array.prototype.reduce() (MDN reference)developer.mozilla.org
- Object.groupBy() (MDN reference)developer.mozilla.org





