Understanding Javascript .reduce() and its use cases
In this article, we’ll explore how the JavaScript Array .reduce()
method works, and some powerful use cases for it.
First, what is it?
The reason I could never wrap my head around this method was failing to understand the word ‘reduce’, Oxford’s definition is:
To make smaller or less in amount, degree, or size.
This is exactly what the reduce function does. It iterates over an array (a group of elements of the same type — ideally) from left to right, and reduces it to a single value.
- Note: the single value could be an object or any primitive type.
The easiest explanation
Imagine an array of numbers:
If we wanted to add all the numbers in the array, we could use a forEach loop:
The forEach method iterates from left to right, starting at 101.2 and adding it to 0; then over 242.2, adding it to 101.2 etc…
The reduce method equivalent, which doesn’t have the messy predefined variable spill over let total = 0
would be:
Here, the reduce method is applied to the numbersToSum
array, upon each iteration — we add the currentNumber
that’s being looped over to the currentTotal
(the current total defaults to 0 in the initial iteration — sort of, explained later).
This happens for every item in the numbersToSum
array, until our total is the sum of all the entries in the numbersToSum
array.
How it works
The reduce method takes in 2 parameters.
First — a callback function that accepts up to four parameters (all optional), namely the:
previousValue
: Whatever the previous iteration returnedcurrentValue
: The current value in the array that’s being iterated overcurrentIndex
: The index of the current valuearray
: The array being iterated over
As the method iterates over the array elements, it allows you to manipulate the previousValue
that will be received by the next iteration. In other words, if we ran:
[1, 2, 3].reduce((previousValue, currentValue) => previousValue + currentValue)
For explanations sake: In the first iteration, we return 0 + 1
, in the second iteration, our previousValue
now equals to 1, and we add it to 2 to alter the next previousValue
. In our last iteration our previousValue
is 3, we add it to our currentValue
of 3 and the reduce method returns a value of 6, the expected total.
The second parameter, following the 4-param callback, is the initialValue
, all this does is it sets the previousValue
used in the first iteration.
The clever actuality:
If you’ve worked with reduce a few times, you’ll know that the reduce function only iterates twice over the [1, 2, 3]
array. It does this for 2 clever reasons:
First, it reduces the iteration count. If it assumes 1
as the initialValue
, this is the same as having the first iteration returning 0 + 1
, so we can correctly skip the first, 0 + 1
, iteration. The first iteration would then be looking at 2
and adding an initialValue
of 1
to it.
Second is typing; because the reduce function can return a single value of any type, assuming the type of element it’s working with using the first entry of the array makes its life easier. On it’s first iteration, if we’re adding an element to some initial value, if the values are not of the same type, it can get messy — see below:
0 + {} !== {} + 00 + {} // returns "0[object Object]"
{} + 0 // returns 0
If we were to initialise our reduce with an object instead, forcing 3 iterations:
[1, 2, 3].reduce((total, number) => total + number, {})// it would return "[object Object]123"
// yuck
When is it used?
If you were to reduce everyone’s use cases for the `reduce` function, it would be that:
The single use case for the `reduce` function is to create clusters of data.
Use case examples
Single cluster: Sum of numbers in an array
[1, 2, 3].reduce((total, number) => total + number)// returns 6
Single cluster: Averaging numbers in an array
[1, 2, 3].reduce((total, number, index, array) => {
total += number // add current number to total
if (index === array.length - 1) { // if we're at the last iter.
return total/array.length // return the average
} return total
})// returns 2 (= 6 / 3)
Single cluster: Flatten array
Say you had an array of blog articles, each containing an array of comments. If you wanted to get a list of all comments, you could use the reduce function:
Multiple clusters: Tally of items
Say we had an array of strings describing fruit:
If we wanted to return an object that would give us a tally of each fruit in the array (not knowing what fruit could possibly be in the array — hence we couldn’t use .filter()
)
We could simply write:
const fruitTally = fruit.reduce((currentTally, currentFruit) => {
currentTally[currentFruit] = (currentTally[currentFruit] || 0) + 1
return currentTally
} , {})// returns {"apple":3,"banana":3,"cherry":2,"mango":2,"apricot":1,"guava":2}
Multiple clusters: Date categorisation
This is perhaps my favourite use case. Say you have a list of a users’ transactions with Unix timestamps and you wanted to group them by month (so a user could open one month, and close the others for better UX)
As it currently stands, the data is too flat to represent in any user friendly way, this could be an array of 1000 entries. We can categorise it neatly using a reducer:
Conclusion
The .reduce()
function allows us to do some pretty neat data manipulation. It really shines when it comes to making data easier to work with, or easier to represent where it needs to be read by humans.