Array.prototype.reduce()
reduce(callbackFn, initialValue) The reduce() method executes a callback function for each element in an array, accumulating all values into a single result. The callback receives four parameters: the accumulator (result from previous iteration), the current element, its index, and the array itself. If an initial value is provided, reduce starts with that as the accumulator and iterates from index 0. Without an initial value, it uses the first element as the accumulator and starts from index 1.
Syntax
reduce(callbackFn)
reduce(callbackFn, initialValue)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
callbackFn | Function | — | Function to execute for each element. Receives accumulator, currentValue, currentIndex, and array. |
accumulator | any | — | Value accumulated across iterations. First call uses initialValue if provided, otherwise array[0]. |
currentValue | any | — | Value of current element. First call uses array[0] with initialValue, otherwise array[1]. |
currentIndex | number | — | Index of current element. Starts at 0 (with initialValue) or 1 (without). |
array | Array | — | The array being reduced. |
initialValue | any | undefined | Starting value for accumulator. If omitted, uses first element. |
Examples
Summing an array with initial value
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15
Summing numbers is the classic introduction, but reduce() really proves its worth when the accumulator is a data structure that grows across iterations. The callback returns the accumulator on each step, so you can build up objects, arrays, or nested maps one element at a time.
Counting occurrences
const names = ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob', 'Alice'];
const counts = names.reduce((acc, name) => {
acc[name] = (acc[name] || 0) + 1;
return acc;
}, {});
console.log(counts); // { Alice: 3, Bob: 2, Charlie: 1 }
Tallying values into an object is a pattern that comes up often in analytics and reporting. The empty-object initial value gives the callback a consistent starting point, and each iteration adds or increments a key.
Common Patterns
Function piping: Chain functions where each output feeds into the next.
const pipe = (...fns) => (initial) => fns.reduce((acc, fn) => fn(acc), initial);
const double = x => x * 2;
const addFive = x => x + 5;
const compute = pipe(double, addFive);
console.log(compute(10)); // 25
Function piping turns reduce() into a composition tool. Each function in the array transforms the accumulator, and the end result is a single function that runs the whole pipeline in order. This pattern is common in middleware stacks and data-processing pipelines.
Promise sequencing: Run async operations in order.
const runPromises = (...fns) => (initial) =>
fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(initial));
const p1 = x => Promise.resolve(x * 2);
const p2 = x => Promise.resolve(x + 3);
runPromises(p1, p2)(5).then(console.log); // 13
Promise sequencing is another case where the accumulator is more than a number. Each promise wraps the previous result, and the chain resolves step by step without nesting.
Without initialValue gotcha: When omitted, the first element becomes the accumulator.
// Works fine
[1, 2, 3].reduce((a, b) => a + b); // 6
// Throws TypeError on empty array
[].reduce((a, b) => a + b); // TypeError
// Safe with initialValue
[].reduce((a, b) => a + b, 0); // 0
When to Avoid reduce
While reduce is powerful, certain tasks have better alternatives. For flattening arrays, use flat() instead. For grouping objects, Object.groupBy() is cleaner and more readable. These methods express intent more clearly and often perform better.
When reduce() Helps
reduce() is best when a list needs to become one result. That result might be a number, a string, an object, or even another function. The common thread is that each element contributes to a single accumulated value, which makes reduce() ideal for totals, lookups, compaction, and pipeline-style composition.
The method is flexible enough to do a lot, but that does not mean it should replace every loop. If the code is just iterating for side effects, a for loop or forEach() is often easier to read. reduce() shines when the accumulator is the point of the code.
Initial values matter
The initialValue argument is what keeps reduce() predictable on empty arrays and clarifies the accumulator type. Without it, the first array element becomes the starting point, which can be fine for simple sums but awkward for object-building or async chaining. When in doubt, provide an initial value so the callback always sees the same shape.
That small habit avoids a lot of edge cases. It also makes the callback easier to test because the first call follows the same pattern as every later call.
Next Steps
Now that you understand reduce(), practice with these challenges:
- Build a shopping cart total calculator using reduce
- Implement your own pipe function to chain array methods
- Try rewriting a for loop as reduce to see the difference
Pair reduce with other array methods like map() and filter() in a single chain for powerful data transformations.
See Also
- Array.prototype.map() — Transform each element
- Array.prototype.filter() — Select elements by condition
- Array.prototype.flat() — Flatten arrays (often replaces reduce for this)