JavaScript has an array function, reduce, that loops through an array and produces a result of any type—number, string, object, or even array.
Why would you need that?
A classic example is computing the sum of an array of numbers:
const numbers = [1, 2, 3, 4]
const sum = numbers.reduce((a, b) => a + b, 0)
console.log(sum) // 10
The alternative is to use a basic .forEach
loop and add each number to a variable:
let result = 0
numbers.forEach(number => {
result += number
})
Whether the latter is better is another topic (we’ll discuss later in this article).
How It Works
Reduce takes two arguments: a callback and an initial value.
The callback has three parameters: accumulator, current value, and current index.
numbers.reduce(
(accumulator, currentValue, currentIndex) => {}
, 0)
You can think of the accumulator as the result variable in the .forEach
loop example above. It’s the variable that we keep updating until we get the final result.
The accumulator starts with the initial value that we pass as the second argument to the reduce function—in this example, the initial value is 0.
The currentValue is the current item of the loop. It’s the same as the parameter number when we used numbers.forEach(number => {})
in the basic .forEach
loop example.
The currentIndex is the current index of the loop, it starts with 0.
In order to update the accumulator, you need to return something from the callback. What you return gets assigned to the accumulator.
If, for example, I returned a static value like 5, the accumulator will be assigned the value 5 on each iteration.
const result = numbers.reduce((accumulator, currentValue) => {
return 5
}, 0)
console.log(result) // 5
In each iteration, the accumulator will have the value returned from the previous iteration—and it will start with initial value from the second argument of reduce.
So in the sum example, the accumulator started with 0, and, in each iteration, we add the current value to it, and then return for the next iteration.
Take another look (with a longer version):
const sum = numbers.reduce((accumulator, number) => {
return accumulator + number
}, 0)
A More Sophisticated Example
My favorite use case for reduce is to transform an array to an object.
In this example, we have a list of fruit names, where there’s a possibility of duplication. The goal is to transform that array into an object where the key is the fruit name and the value is the count of occurrences.
const array = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
const result = array.reduce((acc, item) => {
const count = acc[item] || 0
acc[item] = count + 1
return acc
}, {})
console.log(result)
/*
{
apple: 3,
banana: 2,
orange: 1
}
*/
We can tell that the result will be an object based on the initial value; we used an empty object.
In each iteration, we get the current count from the accumulator. And if it doesn’t exist, we default to 0—that’s what || 0
is for.
Then we increment that by one. And then return the updated accumulator, acc, to update the accumulator for the next iteration.
Reduce Can Be Used for Everything, but You Shouldn’t
It’s tempting to use reduce for all array operations, because reduce can. There’s a certain level of complexity where reduce can make the code more concise, like the fruits example. But in most cases, it can make the code look more complex and harder to understand.
For example:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const evenNumbers = numbers.reduce((acc, num) => {
if (num % 2 === 0) {
acc.push(num)
}
return acc
}, [])
console.log(evenNumbers) // [2, 4, 6, 8, 10]
I don’t see why using reduce for filtering even numbers is better than this:
const evenNumbers = numbers.filter(num => num % 2 === 0)
After all, filter was created for this purpose.
My rule of thumb is to avoid using reduce unless it’s the most concise option. If I need to map the array, I use map. If I need to filter the array, I use filter.
If it’s not one of those and I’m unsure if reduce is the best option, I try the alternatives, like a basic .forEach
loop, and see what reads better.
Why Some Prefer Reduce Over a Basic forEach Loop
Sometimes it’s more concise.
It’s functional. You don’t need to update variables outside reduce; everything is contained within the callback. So it encourages immutability. Also, some people prefer the functional style.
It’s chainable, because it returns a value. Like map and filter, you can chain reduce with other array operations:
anArray
.filter(() => {})
.map(() => {})
.reduce(() => {})
Reduce Is Just a Tool
Now you have a new tool in your JavaScript toolset. You can use it if you want (but you don’t have to). At least now you can understand it when you see it in other codebases.