The reduce()
method on JavaScript arrays executes a "reducer" function on every
element of the array in order, passing the return value from the previous reducer
call to the next reducer call. The reduce()
function is a common cause
of confusion, but it can also make your code much more readable when combined with other functional programming abstractions.
Here are 4 common examples and 1 not-so-common example that demonstrate how to use reduce()
.
Summing an Array of Numbers
Most reduce()
tutorials start with this example: given an array of numbers [1, 3, 5, 7]
, calculate the sum. Here's how you might sum up an array with a plain old for
loop.
function sum(arr) {
let sum = 0;
for (const val of arr) {
sum += val;
}
return sum;
}
sum([1, 3, 5, 7]); // 16
Here's an equivalent example using reduce()
:
function sum(arr) {
const reducer = (sum, val) => sum + val;
const initialValue = 0;
return arr.reduce(reducer, initialValue);
}
sum([1, 3, 5, 7]); // 16
The reduce()
function's first 2 parameters are a function reducer()
and
an arbitrary initialValue
. JavaScript then calls the reducer()
on each
element of the array with the accumulator value as the first parameter.
The accumulator starts as initialValue
, and then JavaScript uses the return
value of each reduce()
call as the new accumulator.
Talk is cheap, show me the code. So here's a quick example of how you might
implement a simplified reduce()
function using for
loops.
function reduce(arr, reducer, initialValue) {
let accumulator = initialValue;
for (const val of array) {
accumulator = reducer(accumulator, val);
}
return accumulator;
}
Summing an Array of Numeric Properties
The reduce()
function by itself is often more confusing than helpful.
If all you need to do is sum an array of numbers, you might be better
off using a for
loop. But, when combined with other array methods like
filter()
and map()
,
reduce()
starts looking more appealing.
For example, suppose you have an array of line items, and you want to calculate
the sum of each line item's total
property.
const lineItems = [
{ description: 'Eggs (Dozen)', quantity: 1, price: 3, total: 3 },
{ description: 'Cheese', quantity: 0.5, price: 5, total: 2.5 },
{ description: 'Butter', quantity: 2, price: 6, total: 12 }
];
Here's one way to add up the line items total
using reduce()
:
lineItems.reduce((sum, li) => sum + li.total, 0); // 17.5
This works, but is less composable. A better alternative is to first
map()
to get the total
.
lineItems.map(li => li.total).reduce((sum, val) => sum + val, 0);
Why is this second approach better? Because you can abstract out the
reducer into a function sum()
, and reuse it wherever you need to sum
up an array.
// Sum the totals
lineItems.map(li => li.total).reduce(sumReducer, 0);
// Sum the quantities using the same reducer
lineItems.map(li => li.quantity).reduce(sumReducer, 0);
function sumReducer(sum, val) {
return sum + val;
}
This is important because, while you think sumReducer()
will never
change, it will. For example, the above code doesn't account for the fact that
0.1 + 0.2 !== 0.3
in JavaScript. This is a common mistake when calculating
prices in an interpretted language. Binary floating points are weird.
So you actually need to round:
const { round } = require('lodash');
function sumReducer(sum, val) {
// Round to 2 decimal places.
return _.round(sum + val, 2);
}
reduce()
makes it easy to reuse logic like sumReducer()
throughout your app
using function chaining. So you can change your logic once rather than searching
through every for
loop in your app.
Find the Maximum Value
While reduce()
is often used for summing, it doesn't have to be.
The accumulator
can be any value: number, null
, undefined
,
array, POJO, even a promise.
For example, suppose you have an array of JavaScript dates, and you want to find the most recent date.
const dates = [
'2019/06/01',
'2018/06/01',
'2019/09/01', // This is the most recent date, but how to find it?
'2018/09/01'
].map(v => new Date(v));
One approach is to sort the array and take the last element in the sorted array. That works, but isn't as efficient as it could be, and sorting an array of dates in JavaScript is non-trivial.
Instead, you can use reduce()
and make your reducer return the most
recent date found so far.
// This works because you can compare JavaScript dates using `>` and `<`.
// So `a > b` if and only if `a` is after `b`.
const maxDate = dates.reduce((max, d) => d > max ? d : max, dates[0]);
Grouping Values
Given an array of objects with an age
property:
const characters = [
{ name: 'Jean-Luc Picard', age: 59 },
{ name: 'Will Riker', age: 29 },
{ name: 'Deanna Troi', age: 29 }
];
How do you return a map that contains how many characters have a given
age
? For example, the correct output on the above array would be
{ 29: 2, 59: 1 }
.
Here's how you can do that with reduce()
.
// Start with an empty object, increment `map[age]` for each element
// of the array.
const reducer = (map, val) => {
if (map[val] == null) {
map[val] = 1;
} else {
++map[val];
}
return map;
};
characters.map(char => char.age).reduce(reducer, {});
Bonus: Promise Chaining
Suppose you have an array of async functions that you want to execute in series.
There is a non-standard promise.series
function
for this, but you can also do this with reduce()
.
const functions = [
async function() { return 1; },
async function() { return 2; },
async function() { return 3; }
];
// Chain the function calls in order, starting with an empty promise.
// In the end, `res` is equivalent to
// `Promise.resolve().then(fn1).then(fn2).then(fn3)`
const res = await functions.
reduce((promise, fn) => promise.then(fn), Promise.resolve());
res; // 3
Moving On
The reduce()
function is a powerful tool. By abstracting out filters
and reducers, you can consolidate common tasks like "summing an array of numbers"
into a separate function for easier refactoring and DRY-er code.