reduce() in JavaScript

It feels like a very long time since I wrote about JavaScript's filter() method, so in the interest of completeness, I'm looping back to cover two other array manipulation methods: reduce(), and map(). Today, it's the turn of reduce(), which is probably one of the more daunting array manipulation tools, but we'll get to that shortly...
Understanding reduce()
Unlike filter() and map(), the reduce() method is a little more difficult to get a handle on. It is basically a method for processing data stored within arrays, by systematically working through the array, and applying a function to accumulate its elements into a single output value. I'll (hopefully!) do a better job of describing it when we come to the examples/etc below, but for now, think of it a little like a collector: it takes in scattered sets of elements and combines them together into something more manageable and unified.
Fundamentals
reduce() belongs to the Array prototype (Array.prototype.reduce()), and so is universally accessible across array instances in JavaScript.
At a minimum, reduce() needs a callback. With an initial value, the callback runs for each present array element; without one, the first present element becomes the accumulator and iteration starts with the next present element. Empty slots are skipped. The callback receives four arguments:
Accumulator
: The accumulated value returned from the last invocation of the callback, or its initial value.Current Value
: The element currently being processed.Index
(optional): The index of the current element.Array
(optional): The arrayreduce()was called upon.
In addition to these, reduce() can also accept an optional second argument, used to set the initial value of the accumulator.
Syntax
I've already talked about the parameters and arguments that reduce() can accept above, so here's the formal syntax:
const result = array.reduce((accumulator, currentValue, index, arr) => {
// Calculate and return the next accumulator here.
return accumulator;
}, initialValue);A Simple Example
The classic example when trying to explain reduce() is to use it to add together all the numbers within an array:
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, current) => acc + current, 0);
console.log(sum);
//=> 15Here, you can see that reduce() traverses the numbers array, adding each number to a cumulative total (acc), which has been set to start at 0.
A More Complex Example
For a more in‑depth example, imagine you have a set of data representing votes for your team's most handsome developer, and you want to tally the votes for each candidate.
const votes = ['John', 'Andy', 'John', 'John', 'Andy', 'Mark'];
const voteCounts = votes.reduce((acc, vote) => {
acc[vote] = (acc[vote] || 0) + 1;
return acc;
}, Object.create(null));
console.log(voteCounts);
//=> { John: 3, Andy: 2, Mark: 1 }Here, we're using reduce() to tally the votes. Object.create(null) gives the accumulator no inherited properties, so a candidate named constructor or __proto__ is counted like any other name. Each property holds the candidate's total. The results are hardly surprising!
For TypeScript, we can give the accumulator an index signature to describe the name‑to‑count mapping. The initial value still uses Object.create(null); the type annotation describes the values, rather than adding a prototype:
const votes = ['John', 'Andy', 'John', 'John', 'Andy', 'Mark'];
const voteCounts = votes.reduce((acc: { [key: string]: number }, vote) => {
acc[vote] = (acc[vote] || 0) + 1;
return acc;
}, Object.create(null) as { [key: string]: number });
console.log(voteCounts);
//=> { John: 3, Andy: 2, Mark: 1 }... I still won though...!
Tips for using reduce()
Versatility
reduce() can be used for more than just calculations on numerics. It can be used to outright transform arrays, or compile data into new structures. The difference to map() is that you have access to the accumulator, so you can create a tighter relationship between array elements.
Initial Value
Whilst it is optional, I would always suggest providing an initial value for the accumulator. That way, you ensure that your reducer behaves predictively, especially with empty or heterogeneous data sources.
Complexity Management
Whilst very powerful, reduce() can very quickly make your code harder to read, especially for complex operations. At the very least, I would expect to see a cheeky comment above anything more complicated than a three‑line reduce(), simply to offer guidance to whoever comes next.
Wrapping Up
When the output needs to depend on values accumulated from earlier items, reduce() is worth considering. Use the initial value to make that output clear, and keep the callback readable enough for the next person to follow.