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.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus