Reduce: the multitool of JavaScript array manipulation

A beginner-friendly guide into the JavaScript’s reduce method. Spiced up with analogies and examples.

Why reduce might be needed?

JavaScript provides some handy Array methods, like, map, filter, includes, some, and every, just to mention a few. This guide to common array methods has examples of several of them working with object keys.

But there’s a sane limit of how many custom methods you can have in a language. Thank goodness JavaScript is not like PHP, where you can just type a sentence and slap parens at the end and et voilà, it probably does what you want:

// Example of a typical native PHP function
plz_make_that_thing_like_backwards_and_then_tally_them_up_after()

Rather than being a collection of functions, JavaScript is a programming languages which provides versatile methods like reduce. Reduce is the Swiss army knife of array methods, a higher order function that lets you build your own tools.

Reduce syntax

Perhaps a helpful way to look at reduce is to compare it to a map, it has two differences:

const foo = ['foo', 'bar'].map((currentValue, index, array) => {
  return // ...
})
 
const bar = ['foo', 'bar'].reduce((prevValue, currentValue, index, array) => {
  return // ...
}, {})
  1. The callback has one extra argument: prevValue, also referred to as accumulator.
  2. The method has a second parameter: initialValue, which is not always needed, but it can be for example an empty object {} or 0.

And this gives it two features map doesn’t have:

  1. Access to the previously processed values during the iteration, or another way of phrasing: reduce is able to keep track of the accumulated state internally.
  2. Possibility to return other data type than array, like object, or anything really, int, string...

Reduce arguments

callback

A function to execute on each element in the array, except for the first, if no initialValue is supplied.

previousValue

The value the callback function produced on its previous iteration. This concept is in the heart of reduce.

currentValue

This is the value of the current iteration, exactly same as in map.

currentIndex

The index. Just as in map.

array

The original array. Just as in map.

initialValue

A value to use as the first argument to the first call of the callback. If no initialValue is supplied, the first element in the array will be used as the initial accumulator value and skipped as currentValue.

Reduce examples

Below are some examples to illustrate how it actually works.

Summing up numbers in an array

The most basic example is to sum up all the values in an array:

const coolNumbers = [69, 420, 666]
 
const holyNumber = coolNumbers.reduce((total, amount) => {
  return total + amount
})
 
console.log(holyNumber) // 1155 🔥

Calculate an average

The formula for an average is: sum of all the values / total amount of items.

Here’s the logic in English:

  • Keep summing up the values total += amount.
  • When hit the last item: divide the total with the item count.
  • Return total.
const moneys = [76.21, 420.69, 86.54]
 
const average = moneys.reduce((total, amount, index, array) => {
  total += amount
  const isLast = index === array.length - 1
 
  if (isLast) return total / array.length
 
  return total
})
 
console.log(average) // 194.48

This is a great example, because it uses all four of the callback’s arguments.

Returning an object

This is useful, and it is the only array method that can do this in JavaScript. Here we need to use the second param of the reduce method, the initialValue.

Maybe we have a nested array structure of ISO-2 country codes and the country names, and we’d like to turn it into an object where the country code is the key, and country’s name the value.

  • Pass in an empty object {} as the second parameter, this will be the value of the accumulator on the first run.
  • The reduce method’s return value should be an object.
  • The accumulator is spread at the end.
const countries = [
  ['ch', 'Switzerland'],
  ['de', 'Germany'],
  ['fr', 'France'],
]
 
const regions = countries.reduce((acc, [countryCode, name]) => {
  return { [countryCode]: name, ...acc }
}, {})
 
console.log(regions)
// { fr: "France", de: "Germany", ch: "Switzerland" }

Find and group same values in an array

With reduce it’s handy to find all occurrences of the same number from an array:

const numbers = [1, 1, 2, 3, 4, 4, 4, 5, 5, 5, 5]
 
const occurrences = numbers.reduce((accumulator, randomNumber) => {
  accumulator[randomNumber] = ++accumulator[randomNumber] || 1
 
  return accumulator
}, {})
 
console.log(occurrences)
// { '1': 2, '2': 1: '3': 1, '4': 3, '5': 4 }

This can be handy for example when you’re trying to visualize some data, or figure out if a set of random numbers actually is random.

Conclusions

There’s a whole debate about whether we should use reduce at all. Some people think it’s too nebulous, because everything that reduce can do can be done with for loops.

I’ve embedded here a really nice episode of the HTTP 203 podcast with the topic of "Is reduce bad?" It’s worth the watch, there’s some nice nicknacks there.

They reason that junior devs don’t use reduce because it’s complex, and properly marinated seniors don’t use reduce because the pendulum has already swung into its other apex.

Also, there’s a certain (false) purity with reduce and array methods that is somehow appealing, by having one long, unbreakable chain where you process things. When using for loops you need set the initial value into a separate variable at first, which is totally fine, but there’s a weird... what do you call it? kind of destructive, OCD driven, need for things to be concise and neatly packaged.

Hope this was helpful. Thanks for reading.