map(), filter() and reduce() in JavaScript.
By: Aditya Dhanraj Tiwari
Tags: JavaScript, Filter, Functional Programming, Maps, Reduce

Make your code more functional.

Photo by Markus Spiske on Unsplash

O bject oriented programming makes code understandable by encapsulating moving parts. Functional programming makes code understandable by minimizing moving parts. — Michael Feathers

map, filter and reduce are essentially just some of the most well-known, easy to use, higher-order functions that run provided callback on each element of an array.

In this article, we will explore how using map(), filter(), and reduce() can help make our code:

1. Easy to comprehend.

2. Less prone to side effects as these function don’t modify the actual array, and instead create a new one.

3. Avoid explicit loops.

Let’s explore and familiarize ourselves with these functions.

Array.map()

Consider a scenario in which we have multiple records of students and each student record has a name, ID, and marks attribute.

let studentRecords = [ {name: 'John', id: 123, marks : 98 },

{name: 'Baba', id: 101, marks : 23 },

{name: 'yaga', id: 200, marks : 45 },

{name: 'Wick', id: 115, marks : 75 } ]

Problem Statement: We are interested in retrieving only the name of the students and all the names should be in caps.

Expected Result:

['JOHN', 'BABA', 'YAGA', 'WICK']

Pretty simple right? There are multiple ways to achieve this. Let’s go through them one by one.

1. Traditional for() loop

let names = [];

for (let index = 0; index < studentRecords.length; index++){

names.push(studentRecords[index].name.toUpperCase());

} console.log(names); // logs: [ 'JOHN', 'BABA', 'JOHN', 'WICK' ] 2. for(...of)

let names = []

for (const student of studentRecords) {

names.push(student.name.toUpperCase());

} console.log(names); // logs: [ 'JOHN', 'BABA', 'JOHN', 'WICK' ] 3. forEach()

let names = []

studentRecords.forEach( student => {

names.push(student.name.toUpperCase());

}) console.log(names); // logs: [ 'JOHN', 'BABA', 'JOHN', 'WICK' ]

In all the above-mentioned approaches, we have to first create an empty array to save our result. Moreover, In both, for(…of) and for() we have to explicitly iterate through the arrays, making our code a lot messier.

Solution using map() →

let names = studentRecords.map( stu => stu.name.toUpperCase()); console.log(names); // logs: [ 'JOHN', 'BABA', 'JOHN', 'WICK' ]

The above piece of code shows how concise and elegant our code becomes when we use the map function and when we don’t need to define an empty array.

Working of map():

Array.map( callback, thisValue )

map() takes two arguments, callback and optional object value (by default the callback is bound to this).

map() executes the provided callback at each element of an array and returns the new value to the resultant array.

When thisValue is not provided, the callback function will bind itself to the object which called it (this value depends on caller expression). In our example “thisValue” will bind itself to “studentRecords”.

Array.filter()

filter() returns only those elements from array which fulfils the provided criteria.

Problem Statement: Suppose we have the same dataset as above but this time we want to get the details of students who scored more than 50 marks.

Expected Result:

[{name: 'John', id: 123, marks : 98 },{name: 'Wick', id: 115, marks : 75 }]

Solution: Now we use filter() to select records which fulfills the given condition (that is, marks > 50).

let names = studentRecords.filter(stu => stu.marks > 50); console.log(names); // logs: [ { name: 'John', id: 123, marks: 98 },{ name: 'Wick', id: 115, marks: 75 }]

Problem Statement: Retrieve the details of students who scored more than 50 marks and have id greater than 120.

let names = studentRecords.filter(stu => stu.marks > 50 && stu.id > 120) console.log(names); //logs: [ { name: 'John', id: 123, marks: 98 } ]

Solution: As shown in the above piece of code, using filter() with multiple conditions to filter data, we can solve this problem very efficiently.

Working of filter()

filter() takes the callback function which returns a bool value. If the provided callback returns true, then the object is added to the resultant array. Otherwise, the object is ignored.

Array.reduce()

Just like map() and filter(), reduce() also executes the callback for each element of the array. To better understand reduce, we first need to understand two terms: “accumulator” and “reducer”.

The accumulator is the value that we end up with, and the reducer is what action we will perform in order to get to one value. You must remember that a reducer will always return only one value, hence the name “reduce”. It’s time to deep dive into an example!

Problem Statement: Consider the same scenario we have discussed above, but this time we would like to know the sum total of the marks of the students.

let totalMarks = studentRecords.reduce( ((acc,emp) => acc+emp.marks), 0) console.log(totalMarks);// logs: 241

Solution: As shown in the above piece of code, using reduce() we were easily able to get the desired result in a simple and robust way.

Working of reduce():

Array.reduce( (accumulator,curr_value) => expression ), intialValue)

Let’s understand the working of reduce step by step:

1. reduce() takes the accumulator, current value, expression and the initialValue as a parameter used to initialize the accumulator (notice that we have passed initialValue = 0 in our example).

2. reduce() takes the first element of an array as the current value and the accumulator, and then performs the required operation (as with the addition of two values in our example), and saves the result in the accumulator.

3. The accumulator is then replaced by the previously calculated accumulator and the current value is set to the second element of the array. Again, the required operation is performed and the accumulator is updated with the new result.

4. Step 3 is repeatedly executed through to the last element of the array.

5. Finally, the accumulator value is returned by the function.

To compare our understanding with the actual working of reduce(), let’s observe the values of the “accumulator” and “curr_value” at each iteration for the above example.

let totalMarks =

studentRecords.reduce( function(accumulator,curr_value){

console.log(`accumulator: ${accumulator}, curr_value:$ curr_value.marks}`);

return accumulator + curr_value.marks;}, 0) console.log(totalMarks); // logs ------- accumulator: 0 , curr_value: 98

accumulator: 98 , curr_value: 23

accumulator: 121 , curr_value: 45

accumulator: 166 , curr_value: 75

241

It is evident from the above logs that the accumulator starts with an initial value of 0 and, at every step, its value is updated by the sum of its “previous value” and “curr_value”.

Now combine the power of map(), filter() and reduce() together

Example 1. map() and filter() together

Problem Statement: This time we want to get only the names of the students who scored more than 50 marks from the same dataset used above.

let names = studentRecords.filter(stu => stu.marks > 50).map(stu => stu.name) console.log(names); // logs: [ 'John', 'Wick' ]

Solution: We first filtered the data using filter() and then used map() to get only the name attribute of the filtered students.

You can chain the map(), filter() as they return the array.

Example 2. filter() and reduce() together

Problem Statement: This time we are required to print the sum of marks of the students with id > 120.

let totalMarks = studentRecords.filter(stu => stu.id > 120).reduce((acc,curr) => acc + curr.marks ,0) console.log(totalMarks); // logs: 143

Solution: In the above code, we have first selected the students with an id greater than 120 using filter(), and then we passed the filtered student array to reduce() to get the sum of their marks.

Note: You can chain the reduce() after map() and filter() as they return an array but can’t chain map() and filter() on reduce as it returns a single value.

Example 3. map(), filter() and reduce() together

Problem Statement → This time we are required to print the total marks of the students with marks greater than 50 after a grace of 15 marks has been added to those students who scored less than 50.

let studentRecords = [ {name: 'John', id: 123, marks : 98 },

{name: 'Baba', id: 101, marks : 23 },

{name: 'John', id: 200, marks : 45 },

{name: 'Wick', id: 115, marks : 75 }, ] let totalMarks = studentRecords.map(function(stu){

if(stu.marks < 50){

stu.marks += 15;}

return stu;

}).filter(stu => stu.marks > 50).reduce((acc,curr) => acc+curr.marks, 0); console.log(totalMarks); //logs : 233

Solution:

Using map(), we add a grace of 15 marks to students who scored less than 50. Then, using filter() on the array of students returned by map() function, we find all the students with marks greater than 50. Finally, we used reduce() on the array of students returned by filter() function to return the sum of the marks of those students.

Conclusion

In this article, we saw how map(), filter() and reduce() can ease the life of a developer by reducing the number of unnecessary explicit loops and empty array declarations. The end result of this is that our code is made more functional and easy to comprehend. Try replacing your for loops with these state of the art functions whenever you get a chance.

Good Luck and Happy Coding 😃