The Array.reduce Method Part 2 - Replacing Map and Filter Chains

Published: Sunday, May 29, 2022

Greetings, friends! This is Part 2 of my tutorial series on the Array.reduce method in JavaScript! In this tutorial, I will discuss how to rely less on the Array.map and Array.filter methods. We won't be replacing them entirely, but we will rely less on chaining them.

Method Chaining

In JavaScript, we can chain certain methods together. Let's see an example.

javascript
Copied! ⭐️
const array = [-1, 1, -2, 2, -3, 3];

const newArray = array.filter(num => num > 0).map(num => num * 2);

console.log(newArray); // [2, 4, 6]

In the example above, we are chaining together a map method and filter method. The filter method is removing all negative numbers and returning a new array. The map method then doubles every element in this new array and returns it.

We could have also declared functions and passed them into the map and filter methods instead of using arrow functions.

javascript
Copied! ⭐️
function removeNegatives(num) {
  return num > 0;
}

function doubleNumbers(num) {
  return num * 2;
}

const array = [-1, 1, -2, 2, -3, 3];

const newArray = array.filter(removeNegatives).map(doubleNumbers);

console.log(newArray); // [2, 4, 6]

Note that method chaining is possible because the filter method returns an array, and the map method needs an array to work. You can't chain map or filter after forEach because forEach returns undefined.

javascript
Copied! ⭐️
const array = [1, 2, 3];

array.forEach(num => num * 2).map(num => num * 2);

/* ERROR:
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
*/

Replacing Map and Filter Chains with Reduce

Let's think about what happens we chain together map and filter methods.

javascript
Copied! ⭐️
// Iterates through six elements
const filteredArray = [-1, 1, -2, 2, -3, 3].filter(num => num > 0);

console.log(filteredArray); // [1, 2, 3]

The filter method will iterate through the entire array once. Then, we call the map method to double every element.

javascript
Copied! ⭐️
// Iterates through three elements
const finalArray = filteredArray.map(num => num * 2);

console.log(finalArray); // [2, 4, 6]

We had 6 iterations when applying the filter method and 3 iterations when we applied the map method for a total of 9 iterations. This is also the optimal order of operations. Imagine if we did the map method first followed by the filter method.

javascript
Copied! ⭐️
// Iterates through six elements and returns an array of six elements
const doubledArray = [-1, 1, -2, 2, -3, 3].map(num => num * 2); // [-2, 2, -4, 4, -6, 6]

// Iterates through six elements again and returns an array of three elements
const finalArray = doubledArray.filter(num => num > 0); // [2, 4, 6]

That would have been a total of 12 iterations! This doesn't seem like much, but imagine if we had hundreds or thousands of elements in an array! Wouldn't it be nice to iterate through the original array just once? Then, we'd always be iterating through an array equal to the number of elements in that array.

The reduce method to the rescue! Let's replace a .filter.map chain with one call to the reduce method instead.

javascript
Copied! ⭐️
const array = [-1, 1, -2, 2, -3, 3];
const initialValue = [];

// Iterates through six elements
const newArray = array.reduce((accumulator, currentValue) => {
  if (currentValue > 0) {
    const doubled = currentValue * 2;
    accumulator.push(doubled);
  }
  return accumulator;
}, initialValue);

console.log(newArray); // [2, 4, 6]

Let's dive into what's happening in the code snippet above. The most important thing to notice is that we are passing in initialValue as the second parameter of the reduce method. This value is set to an empty array, which means the accumulator will be equal to an empty array during the first iteration of the reduce method.

Now, let's look at the reducer, the callback function we pass into the reducer method. We check if currentValue is greater than zero. During the first iteration of the reducer method, currentValue will be equal to -1. Therefore, the if condition fails and we simply return the accumulator which is still set equal to an empty array.

During the second iteration, currentValue is now equal to 1, which is greater than 0. Therefore, we double this value to 2 and push this number to accumulator. Now, accumulator is no longer an empty array and contains one value.

This pattern continues until we have iterated through the entire array. The final value stored in accumulator will be an array that contains [2, 4, 6], which is the same array we got when we chained together the filter and map methods in earlier examples.

Here is a tabular breakdown of what happens during each iteration of the reduce method:

iterationaccumulatorcurrent valuereturn value
1st-1
2nd12
3rd2-22
4th222, 4
5th2, 4-32, 4
6th2, 432, 4, 6

Instead of iterating 9 times by calling Array.filter.map in our earlier example, we only had to iterate 6 times, or the length of the original array. This may not seem like much, but scale up the array to hundred or a thousand times, and you'll surely notice a big difference in performance.

Conclusion

I hope you understand why learning about the reduce method is so crucial. We can drastically improve the performance of our code by rewriting .filter.map method chains using the reduce method. The reduce method is very powerful, but it does require reconstructing our logic a bit. This method isn't limited to JavaScript either. Multiple programming languages such as Ruby and C# implement their own version of the reduce method. Learn how to create powerful reducers and you can reduce the time it takes your code to run 😉.

Resources