JavaScript Reduce: The Ultimate Iterator
By: Mike Pottebaum
Tags: Javascript Tips, JavaScript, Javascript Development, Programming, Web Development

I completely underestimated the power of Array.prototype.reduce() when I was first learning JavaScript. I didn’t totally understand how it worked, and so I wrote it off as nothing more than a convenient way to find the sum of an array’s elements.

But now that I understand how reduce works, I see its potential for all kinds of different applications. To demonstrate this potential, I’ll first use reduce to mimic the behavior of the two most common array methods I use — map and filter — and then show how it can be used in place of for loops to nicely clean up code.

Reduce as Map

For the uninitiated, Array.protoype.map() is a method you can use on any array to run the same code on each of its elements to produce a new, transformed array. It looks like this ( >> indicates the returned value):

[1, 2, 3].map(num => num + 1)

>> [2, 3, 4]

The method .map() accepts a callback function as its first parameter — here I’m passing an anonymous function — and passes each element in the array as the first argument to that callback function.

The same result can be achieved with reduce like this:

[1, 2, 3].reduce((result, num) => [ ...result, num + 1 ], [])

>> [2, 3, 4]

Ok, so what’s happening here? The first parameter of the callback function passed to reduce is the accumulator, the value ultimately returned by reduce . Each time reduce runs this callback function, a new accumulator is returned and passed into the next callback function, and so on until it reaches the end of the array. Th;;

The callback function’s second parameter is the actual element in the array. So, what I’m doing above is copying the accumulator into a new array and adding the current array element into the new array. This new array becomes the new accumulator that is then passed into the callback function.

The reduce method is also different from map because it accepts an optional second argument, which is used as the initial value of the accumulator. In my example, I passed an empty array.

Reduce as Filter

Array.prototype.filter() has a similar structure to map , but the result is very different. The callback function passed to filter is used to determine which values appear in the new returned array and which values do not.

It looks like this:

[1, 2, 3].filter(num => num > 1)

>> [2, 3]

When the callback function returns false , the element being evaluated is omitted from the returned array.

Using reduce , it looks like this:

[1, 2, 3].reduce((result, num) => {

return num > 1 ? [ ...result, num ] : result

}, [])

>> [2, 3]

I mimicked the behavior of filter by returning a ternary statement. If num > 1 evaluates to true , return a new array with the contents of the accumulator and the current value of num . If the expression evaluates to false , return the accumulator as it is, omitting the current value of num from the returned array.

The Power of Reduce

Now that we have a better idea of how to use reduce to imitate the behavior of other array methods, I want to demonstrate how to use reduce to replace somewhat ugly code that I find myself writing often (usually when solving problems on Code Wars or Hacker Rank).

First, let’s look at the scenario. We need to go through an array and remove any duplicates, returning a new array with only the unique values from the original array.

As complexity conscious programmers, we know that the brute force solution — iterating over the whole array for each value or, in other words, nesting an iterator within another iterator — results in quadratic complexity because you can calculate the time it will take to do this by squaring the length of the original array.

Instead of searching through the entire array for each value within that array, we can create an object to serve as a library we can use to instantly look up a value and see if it has already occurred. When I was first learning to code, I wrote this solution like this:

const unique = array => {

const lib = {}

const result = [] for(let i=0; i < array.length; i++) {

if(!lib[array[i]]) {

result.push(array[i])

lib[array[i]] = true

}

} return result

} unique([1, 2, 3, 3, 4, 5, 5]) >> [1, 2, 3, 4, 5]

This code gets the job done, but it doesn’t look great. Let’s see how we can clean it up with reduce :

const unique = array => {

return array.reduce(

(result, num) => {

if(!result.lib[num]) {

return {

lib: { ...result.lib, [num]: true },

arr: [ ...result.arr, num ]

}

} else {

return result

}

},

{ lib: {}, arr: [] }

).arr

} unique([1, 2, 3, 3, 4, 5, 5]) >> [1, 2, 3, 4, 5]

Ok, so maybe the result doesn’t look super clean, but I think this example illustrates the vast potential of reduce because it shows that the accumulator can be as complex as you need it to be. You can store whatever information you need to in the accumulator. Just remember to always return a value, otherwise undefined will replace your accumulator and might cause some frustrating confusion.

Enjoyed this article? If so, get more similar content by subscribing to Decoded, our YouTube channel!