A Definitive Guide To map(), filter(), and reduce() in JavaScript11 min read

The concept of functional programming becomes more and more prevalent in mainstream languages nowadays. Even Java has lambda expression and a lot of built-in functional techniques, so why not JavaScript, right?

In this article, we are going to delve deep into some of the most common functional techniques in JavaScript, which are .map(), .filter(), and .reduce() methods. Those methods are used widely in array manipulations and they empower your code to look neater, concise, and readable. We call those methods are functional techniques because instead of giving a step-by-step instruction, we just dictate JavaScript what to do (i.g let “map” something, let “filter” something, or let “reduce” something).

If you are not sure about the difference between imperative and declarative (functional) programming, check out this article.

Overview

First off, for a vivid introduction, let’s see a funny tweet describes those 3 methods with emoji:

When learning those 3 methods we easily stumble upon some concepts such as a higher-order function or callback function. A higher-order function is a function receives another function as its argument, and a callback function is a function passed as an argument of another function, clear enough?

The .map() method

In math, for example, we have a function  f(x) = 2x , we put a number 2 to this function and it will give us 4, and put 3 inside this function will give us the result of 6, for any positive number, the mapping process of this function gives us a doubled value of a number we put on. The .map() method in JavaScript kind of work the same way.

Syntax

The .map() method is used to create a new array from the existing one based on the expression we provide in the callback function, the callback function here is the first argument of this .map() method, the syntax is as the following:

var newArr = arr.map(function callback(element, index, array) {
    // Return value for newArr
}[, thisArg])

Now let’s break down the syntax and explain every element of this .map() method:

  1. arr is the array that we use the .map() method on
  2. Inside the map method, there are 2 parameters:
    1. The callback function, which accepts 3 parameters:
      1. the first parameter is the currentValue, which is the current element being processed in the array.
      2. the second one is the index (optional), it is the index of the current element in the array.
      3. the last one array also optional, which is the array that the .map() method was called upon
    2. The second parameter of the .map method is thisArg (optional) indicates the this value when executing the callback.

Typically, when working with the map method, we just have to provide the currentValue of the callback function, all of the other parameters are completely optional hence we can omit them.

Examples

We can easily double each number inside an array with the .map() method:

var arr = [2, 4, 5, 6];

var newArr = arr.map(function(x) {
    return x * 2;
});
console.log(newArr); // [ 4, 8, 10, 12 ]

How it works:

The callback function is called every time for every element of our arr. Each time the callback function gets executed, the returned value will be added to the newArr.

The index and array parameters are optional, most of the time you don’t need to include them into your .map() method.

You can also use the arrow function syntax for the callback to make your code more concise:

var newArr = arr.map(x => x * 2);

The code has been significantly shortened when compared with traditional for loop:

var arr = [2, 4, 5, 6];
var newArr;
for (let i = 0; i < arr.length; i++) {
    newArr[i] = arr[i] * 2;
}
console.log(newArr)

Here is another example, we store some objects in an array to schedule what we need to do in a day, and we want to extract task name of all our tasks, this can be effortlessly done with the .map method:

var todos = [{
        task: "Learning JavaScript",
        priority: "high",
        duration: 60,
    },
    {
        task: "Walking",
        priority: "medium",
        duration: 30,
    },
    {
        task: "Reading something",
        priority: "low",
        duration: 10,
    }
]
var taskNames = todos.map(x => x.task);
console.log(taskNames) // [ 'Learning JavaScript', 'Walking', 'Reading something' ]

You should keep in mind that all methods we learn in this article only work with arrays. In order to get the name of each task, we just have to access the task property of each object. Every time the callback function is invoked, the x parameter is the current value, which itself is an object, we simply use the dot notation to reach the task property of this object and the returned value will be added to the taskNames.

Also read:

The .filter() method

As the name of this method implies, the filter method in JavaScript creates a new array of elements that pass the conditions provided by the callback function. If the condition on this element returns true, then this element is pushed to the new array, otherwise, this will not be pushed to the new array.

Syntax

Here is the syntax of the .filter method:

var new_array = arr.filter(function callback(element, index, array) {
    // Return true or false
}[, thisArg])

The number of parameters in the filter method is identical to the map method. The only requirement in this filter method is the element – which you need to pass to the callback function.

Examples:

Now, let’s see how we can use the filter method in action. We want to calculate the squared of each element in an array and elements with values greater than 100 will be eligible for pushing into a new array:

var nums = [5, 4, 13, 2, 9, 10, 12, 24];
var squaredGreaterThan100 = nums.filter(x => x * x > 100);
console.log(squaredGreaterThan100); // [ 13, 12, 24 ]

As we can see, we only get the elements with the squared value greater than 100 in our new array.

How it works:

  • First, we create an array nums to perform the filter method.
  • Then we create a new array, which will store the result after the filtering process.
  • We call the filter method on the nums array, pass to it a variable x, which is the current value being processed on the num array.
  • This callback function is called every time an element is reached, then this element is passed to the callback function. If the element passes the test condition, which means the callback function returns true, then this element will be added to the new array.
  • How the callback gets called is done internally, we don’t have to worry about this.

Now we examine one more example, here the filter method is used to filter out all the countries with a population less than 100 million:

var countries = [{
        countryName: 'United States',
        population: 329064917
    },
    {
        countryName: 'Japan',
        population: 126860301
    },
    {
        countryName: 'Germany',
        population: 83517045
    },
    {
        countryName: 'Canada',
        population: 36471769
    }
]
var countriesWithOver100Million = countries.filter(country => country.population > 100000000);
console.log(countriesWithOver100Million);

Let’s break down the filter method once again:

  • The countries variable is an array and inside this array are some objects which describe some countries and their population.
  • We want to get all the countries with a population greater than 100 million people. 
  • We apply the filter method on countries variable, inside the callback function, we only provide one required parameter country. This callback function will be called every time on each of its elements. 
  • The first pass country will be the first element inside this countries variable, which is an object with ‘United States’ is the value of its countryName property.
  • We use the dot notation to access the population of each object. If the current object has population >= 100 million, the condition is evaluated to true and then this object is pushed to the new array.

Output:

[ 
  { countryName: 'United States', population: 329064917 },
  { countryName: 'Japan', population: 126860301 } 
]

You can also accomplish the same thing with traditionally for loop, but you have to give a detailed instruction hence the code is much verbose:

var countriesWithOver100Million = [];
for (let i = 0; i < countries.length; i++) {
    if (countries[i].population > 100000000) {
        countriesWithOver100Million.push(countries[i]);
    }
}
console.log(countriesWithOver100Million);

The .reduce() method

The .reduce() method reduces the array into a single value by executing the reducer function. This function accepts a callback function (which is the reducer function) as the first argument, its syntax looks a little bit different compared to map and filter.

Syntax

arr.reduce(callback( accumulator, currentValue[, index[, array]] )[, initialValue])

This method accepts 2 parameters, a callback function, and an optional initial value. The callback function will be called for every element in the array.

This callback function takes 4 parameters, let’s break down the syntax of this callback (reducer function):

  • accumulator – stores the returned value of the previous iteration.
  • currentValue – the current item being processed of this array.
  • index (optional) – the index of the current element being processed in the array.
  • array (optional) – the original array on which the `reduce` method was called.

Again, the second parameter initialValue is optional in the reduce method. If it is provided, it will be used as the initial accumulator value in the first call on the callback function.

Examples

The typical example of the reduce function is to sum all elements of an array

var arr = [2, 4, 5, 6, 12, 9];
var reducer = (accumulator, currentValue, currentIndex, array) => accumulator + currentValue;
// 2 + 4 + 5 + 6 + 12 + 9
var sum = arr.reduce(reducer);
console.log(sum); // 38

The currentIndex and array are optional. We can also define the reducer function right inside the reduce function if we want:

var sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue);
console.log(sum); // 38

How it works

  • We create a variable arr which stores some number elements.
  • Then we call the reduce method on this array to get the total sum of all of its elements.
  • Enclose the reduce method, there is only one callback function, which is reducer, and this reducer function will be called on every element in this array.
  • This reducer function takes 2 required parameters, which are accumulator and currentValue. For example, in the first call for the first element, the accumulator will get a value of 0, and the current value is 2. Adding them together yields the result of 2.
  • The result value of the first function call will become the accumulator for the second and so on…

For better envision, here is a table represents the current value, also an accumulator of each pass and how reduce method formulates the final result:

You can provide the initial value if you want, this value will be added to the accumulator in the first function call:

var arr = [1, 2, 3, 4, 5];
var reducer = (accumulator, currentValue) => accumulator + currentValue;
var sum = arr.reduce(reducer, 10);
// 10 + 1 + 2 + 3 + 4 + 5
console.log(sum); // 25

Using .map(), .reduce(), .filter() all at once

Most of the time, those 3 methods can be used rhythmically together. For example, we have the following array containing some rectangle objects. Each object has 3 properties name, breath, height:

var rectangles = [{
        name: 'Reactangle A',
        breadth: 20,
        height: 10
    },
    {
        name: 'Reactangle B',
        breadth: 12,
        height: 14
    },
    {
        name: 'Reactangle C',
        breadth: 9,
        height: 6
    },
    {
        name: 'Reactangle D',
        breadth: 19,
        height: 4
    }
]

We want to calculate the area of each rectangle (we can use the map method here), and then we want to keep rectangles with area > 100 (use filter) in the new array and then we compute the sum of all rectangles with area > 100 (use reduce).

Since both .map() and .filter() method return arrays, we can easily chain 3 methods together neatly to get our desired outcome:

var rectsSum = rectangles
    .map((rectangle) => rectangle.breadth * rectangle.height) // [ 200, 168, 54, 76 ]
    .filter(area => area > 100) // [ 200, 168 ]
    .reduce((accumulator, currentValue) => accumulator + currentValue);
console.log(rectsSum); // 200 + 168

Let’s break down what’s happening here:

  • First off, we invoke the map method on rectangles, the anonymous callback function is right inside this method and helps us calculate the area of each object. After all, it returns us an array [ 200, 168, 54, 76 ]
  • Then with the result, we’ve obtained with the map method, we use the filter method to get all the elements with value > 100. Again, it returns back to us an array [200, 168]
  • With the result of the previous method, we call the reduce function on this array, which gives us the total sum of all rectangles with area > 100. Finally, we get the concluding result of 368.

Here is the code to achieve the same thing using for loops and some conditional statements. It’s just a bunch of extra work:

var rectsSum = 0;
var areas = [];
for (let i = 0; i < rectangles.length; i++) {
    areas[i] = rectangles[i].breadth * rectangles[i].height;
}
var filteredAreas = [];
for (let i = 0; i < areas.length; i++) {
    if (areas[i] > 100) {
        filteredAreas.push(areas[i]);
    }
}
for (let i = 0; i < filteredAreas.length; i++) {
    rectsSum += filteredAreas[i];
}
console.log(rectsSum); // 368

Summary

In this article, we have learned how to use map, filter, and reduce methods. Those methods are actually some Array’s prototypes that intrinsically exist for the usage of array manipulation. When using those methods, we dictate what to do to achieve our goal rather than giving detailed instructions on how to do it, hence they are considered functional techniques. Each method accepts the callback function as the first argument. Some of their parameters are required, but some are legitimately omitted if you want. By using those methods, your code looks concise, aesthetic, easy to refactor, and understand at a first glance.

Previous Article
Next Article

Sign up for newsletter

* indicates required

Categories

Archives