JavaScript’s Map, Reduce, and Filter

As engineers we build and manipulate arrays holding numbers, strings, booleans and objects almost everyday. We use them to crunch numbers, collect objects, split strings, search, sort, and more. So what’s the preferred way to traverse arrays? For years it’s been the trusty for loop which ensures iterating over the elements in order by index.

In 2011, JavaScript introduced map, reduce, and filter as powerful alternatives when translating elements, finding cumulative values, or building subsets based on conditions. These methods help the developer manage less complexity, work without side effects, and often make code more readable.

In this post we’ll cover the usefulness of Array’s map, reduce, and filter methods. You’ll see use cases, code samples, behavior, and parameters of each method.

shell400-2.jpg
reduce() can find a Fibonacci sequence in O(n)

 Drawbacks of Looping

Say I’m working on a bug fix and wading thru the 1,000s of lines of JavaScript in our codebase. I come across a for loop. Not exactly sure what it’s doing, I take a closer look.

for(var i = 0; i < array.length; i++) {
    if(array.indexOf(array[i]) === i) {
        models.push(array[i]);
    }
}

We can see it traverses an array and inserts non-duplicate elements into a new array. But to figure this out we had to glean these 5 things.

code piece meaning
var i = 0 starts at left side of array
i < array.length finishes at right side of array
i++ increments by one
array.indexOf(array[i]) === i if value is first instance in array, it’ll match index. okay this means it’s checking if it’s a duplicate
models.push(…) models must be a list. But what data’s in it? What are their data types? I must search the file for “models”. Rinse. Repeat.

We needed to check 5 pieces of information to determine what was going on. And this is a single for loop!


 A Functional Approach

This same effect could be written using JavaScript’s built-in filter() method.

var uniqueProducts = array.filter(function(elem, i, array) {
        return array.indexOf(elem) === i;
    }
);

Simple and elegant.

Seeing filter communicates code behavior so I know exactly what it’s doing. Compared to the looping approach above:

In sum, map, reduce and filter makes code less complex, without side effects, and often more readable.

Let’s look at each.


 map()

Use it when: You want to translate/map all elements in an array to another set of values.

Example: convert Fahrenheit temps to Celsius.

var fahrenheit = [0, 32, 45, 50, 75, 80, 99, 120];

var celcius = fahrenheit.map(function(elem) {
    return Math.round((elem - 32) * 5 / 9);
}); 

// ES6
// fahrenheit.map(elem => Math.round((elem - 32) * 5 / 9));

celcius //  [-18, 0, 7, 10, 24, 27, 37, 49]

What it does: Traverses the array from left to right invoking a callback function on each element with parameters (below). For each callback the value returned becomes the element in the new array. After all elements have been traversed map() returns the new array with all the translated elements[1].

parameters:

array.map(function(elem, index, array) {
      ...
}, thisArg);
param meaning
elem element value
index index in each traversal, moving from left to right
array original array invoking the method
thisArg (Optional) object that will be referred to as this in callback


 filter()

Use it when: You want to remove unwanted elements based on a condition.

Example: remove duplicate elements from an array.

var uniqueArray = array.filter(function(elem, index, array) {
        return array.indexOf(elem) === index;
    }
);

// ES6
// array.filter((elem, index, arr) => arr.indexOf(elem) === index);

What it does: Like map() it traverses the array from left to right invoking a callback function on each element. The returned value must be a boolean identifying whether the element will be kept or discarded. After all elements have been traversed filter() returns a new array with all elements that returned true[2].

It has the same parameters as map()

parameters:

array.filter(function(elem, index, array) {
      ...
}, thisArg);
param meaning
elem element value
index index in each traversal, moving from left to right
array original array invoking the method
thisArg (Optional) object that will be referred to as this in callback


 reduce()

Use it when: You want to find a cumulative or concatenated value based on elements across the array.

Example: Sum up orbital rocket launches in 2014.

var rockets = [
    { country:'Russia', launches:32 },
    { country:'US', launches:23 },
    { country:'China', launches:16 },
    { country:'Europe(ESA)', launches:7 },
    { country:'India', launches:4 },
    { country:'Japan', launches:3 }
];

var sum = rockets.reduce(function(prevVal, elem) {
    return prevVal + elem.launches;
}, 0);

// ES6
// rockets.reduce((prevVal, elem) => prevVal + elem.launches, 0); 

sum // 85

What it does: Like map() it traverses the array from left to right invoking a callback function on each element. The value returned is the cumulative value passed from callback to callback. After all elements have been traversed reduce() returns the cumulative value[3].

parameters:

array.reduce(function(prevVal, elem, index, array) {
      ...
}, initialValue);
param meaning
prevValue cumulative value returned thru each callback
elem element value
index index of the traversal, moving from left to right
array original array invoking the method
initialValue (Optional) object used as first argument in the first (leftmost) callback.


 Extras

It’s worth noting for loops still definitely have a place when working with large arrays (e.g. over 1,000 elements) or needing to break the traversal if a condition is met.

In this post we covered how map(), reduce(), and filter() more easily communicate code behavior and likely reduces the need to track side effects. We also covered use cases, code samples, behavior, and parameters of each method. I hope this was useful for you.


 References

Image: Wentletrap Shell

[1] Array.prototype.map ECMAScript 6.0

[2] Array.prototype.filter ECMAScript 6.0

[3] Array.prototype.reduce ECMAScript 6.0

 
1,420
Kudos
 
1,420
Kudos

Now read this

SQL Performance of Join and Where Exists

Sometimes we need to identify record sets with at-least-one relationships. Find customers who’ve placed an order, products categorized as books, or cities that have airports. In this post we’ll compare the performance and execution paths... Continue →