Mastering Map, Filter, and Reduce in JavaScript: A Comprehensive Guide

Mastering Map, Filter, and Reduce in JavaScript: A Comprehensive Guide

Let's understand the most important array methods without going into unnecessary details, to give you a head-start in applying them when required.

Introduction

JavaScript is a versatile programming language that offers several built-in functions for data manipulation and processing. Three of the most commonly used functions for working with arrays in JavaScript are map, filter, and reduce. These functions can be used to transform, filter, and aggregate data in an array concisely and efficiently. In this blog post, we will dive into the details of each of these functions and explore how they can be used to solve common problems in JavaScript programming.

Whether you are a beginner or an experienced developer, understanding map, filter, and reduce can help you write more efficient and maintainable code.

Map: method to apply logic over an array

Introduction to Map —

As the name suggests, the Map in Javascript is used to map or apply a particular logic or function over an array.
Applying logic over an array means, we're processing each element in a particular array through a particular set of instructions and storing the modified version of that element in a separate array. Here, we need to process every element, so we have to traverse through the whole array i.e. we need to iterate over it, hence the map method is also referred to as a type of iterative method.

Syntax —

array_name.map((element, index) => { /* your logic */ })

Here,
Element: it's the current element that is there inside the map, on a given instance of iteration.
Index: it's the current index of the element inside the map, it starts with 0 and goes till n-1 if we take the length of the array to be n

Let's take a simple example of doubling the given array using the map function to understand the different syntaxes, there are multiple syntaxes of Map that you can use, but mostly you would be using these:

  1. Arrow Functions - here you would be defining an arrow function inside the map function's argument.
const arr = [1, 2, 3, 4, 5];
const doubledArray = arr.map((num) => num*2);
console.log(doubledArray); // [2, 4, 6, 8, 10]
  1. Explicit Callback function - by this method you can write your logic in a separate function and can pass it as a callback function argument inside the map.
const arr = [1, 2, 3, 4, 5];
const double = (num) => num*2;
const doubledArray = arr.map(double);
console.log(doubledArray); // [2, 4, 6, 8, 10]

Examples:

Let's take an example of applying logic over an array — we need to take an array of strings containing names of people, and we need to create an array that greets each person in the name array.

const names = ["Anshuman", "Vishal", "Naman", "Aashutosh"];
const greetedNames = names.map(name => (`Hello ${name}, hope you're doing good!!`))
console.log(greetedNames); // ["Hello Anshuman, hope you're doing good!!", "Hello Vishal, hope you're doing good!!", "Hello Naman, hope you're doing good!!", "Hello Aashutosh, hope you're doing good!!"]

In practice Examples

map() comes in handy and useful when you're dealing with the kind of data which has a length property, it makes the code concise and more readable.
Some real-life examples where a map proves to be useful and is used in practice are:

  • Converting an array of objects to an array of specific property values: The map method can be used to extract a specific property from an array of objects and return an array of those property values. For example, in a list of products, you can extract their names and return an array of those names:
const products = [
  { name: 'iPhone', price: 999 },
  { name: 'MacBook', price: 1499 },
  { name: 'iPad', price: 599 }
];

const productNames = products.map(product => product.name);
console.log(productNames); // Output: ['iPhone', 'MacBook', 'iPad']
  • Manipulating array of values: The map method can be used to modify each element of an array based on a specific logic or calculation and return a new array with the modified elements. For example, you can apply a discount to each product price and return a new array with the discounted prices:
const products = [10, 20, 30, 40];
const discountedPrices = products.map(price => price * 0.8);
console.log(discountedPrices); // Output: [8, 16, 24, 32]
  • Displaying a whole list of items in an object or array in an efficient manner without actually hardcoding all the specific HTML Elements, knowledge of map() comes in handy, when you're learning or working in react.
    (Note: You can omit this example if you've no experience with react, you can come back later when you start working in react to have a reference of what I mean here.)
import { movies } from "./data";

const moviesList = () => {
    return (
        <div>
            <ol>
                {
                    movies.map((movie) => (
                        <li key={id}>
                            {movie.title} was directed by {movie.director}
                        </li>
                    ))
                }
            </ol>
        </div>
    );
};

export default moviesList;

Here, we're just importing data from our data file present in the parent directory of the current component file, and returning a list that contains all the movies title and director information inside the HTML tag without actually defining the tags one by one.


Filter: filter the required data as per the given logic

Introduction to Filter:

When we work with data or information in JavaScript, we might have a lot of information that we don't need or want to use, we can use a filter in JavaScript to traverse through all the information and only show us what we want to see. This helps us to work more efficiently and effectively.

In simpler terms, filter, as the name suggests is used to filter only the required data, we can apply a set of instructions or logic to a particular data, and filter out those elements which satisfy the given logic.

Syntax —

The syntax of the filter is pretty much like the map which we've discussed above, but here one thing is different when you pass a function as an argument to filter(), the function is called on each element of the original array. If the function returns a truthy value for a particular element, that element is included in the new filtered array. If the function returns a falsy value, the element is excluded from the new filtered array.

filter((element, index, array) => { /*your logic*/ })

Here index and array are optional arguments that can be used if there is any need for them in some particular situation, where the element represents the current element at a particular iteration, the index is the current index of the current element at a particular iteration, and array represents the array on which the filter is being applied.

Let's take an example to filter out all the elements which are greater than zero (0), from this example we would be learning how to use the filter function and the syntax.

  1. Arrow Functions - here you would be defining an arrow function inside the filter function's argument.
const arr = [-1, -2, 0, 2, 1];
const greaterThanZero = arr.filter((num) => num>0);
console.log(greaterThanZero); // [2, 1]
  1. Explicit Callback function - by this method you can write your logic in a separate function and can pass it as a callback function argument inside the map.
const arr = [-1, -2, 0, 2, 1];
const positive = (num) => num>0;
const greaterThanZero = arr.filter(positive);
console.log(greaterThanZero); // [2, 1]

Examples:

Given an array of names, we need to find those names having lengths greater than 5 characters.

const names = ["Anshuman", "Manan", "Madhu", "Aditya", "Neeshu", "Ashok", "Harsh"];
const greaterThanFive = names.filter((name) => name.length>5);
console.log(greaterThanFive); //['Anshuman', 'Aditya', 'Neeshu']

In this example, all those names having more than 5 characters will return a truthy value and hence will get included in our new filtered array of names with length greater than 5.

— Let's look at another example where we need to separate all those elements which are on even indices, i.e. on 0th, 2nd, 4th ... etc, indices.

const numbers = [8, 3, 5, 1, 7, 6, 4, 9];
const evenIndexNumbers = numbers.filter((num, index) => index%2===0);
console.log(evenIndexNumbers); //[8, 5, 7, 4]

Here, we are making use of the 2nd argument of the filter function, i.e. the index, and it's important to note that, to use the index argument we must use the num argument before the index argument else we would not be able to make use of index argument.

In practice Examples

  • Filtering an array of products based on certain criteria: If you have an array of products and you want to filter out only the ones that meet certain criteria (such as being in a certain price range or having a certain name), you can use the filter() method. For example:

      const products = [
        {name: 'Apple', price: 1.99},
        {name: 'Banana', price: 0.99},
        {name: 'Orange', price: 2.49},
        {name: 'Grapes', price: 3.99},
        {name: 'Watermelon', price: 5.99}
      ];
    
      const expensiveProducts = products.filter((product) => product.price >= 3);
    
      console.log(expensiveProducts); // Output: [{name: 'Grapes', price: 3.99}, {name: 'Watermelon', price: 5.99}]
    

    In this example, the filter() method is used to extract only the products that have a price of 3 or higher.

  • Filtering out duplicates from an array: If you have an array of items and you want to remove duplicates from it, you can use the filter() method. For example:

      const items = ['apple', 'banana', 'apple', 'orange', 'banana', 'grapes', 'orange'];
      const uniqueItems = items.filter(function(item, index, arr) {
        return arr.indexOf(item) === index;
      });
    
      console.log(uniqueItems); // Output: ['apple', 'banana', 'orange', 'grapes']
    

    In this example, the filter() method is used to remove any duplicates from the items array. The function passed to filter() checks whether the current item is the first occurrence of that item in the array, and returns true only for the first occurrence. This results in a new array uniqueItems that contain only the unique items from the original items array.


Reduce: returns a single value after processing the whole array

Introduction to Reduce -

Reduce is yet another array method just like map and filter that we have discussed above, but it's the most special one and can be used more flexibly than map and filter to process the array.

As the name is indicating "reduce", reduces a long array or anything having a length property into a single instance or a particular datatype after applying the logic passed into the reduce function as an argument, for instance - finding the sum of all the elements of an array can be calculated using reduce.

Syntax —

reduce((accumulator, currentValue, currentIndex, array) => { /* your logic */ }, accumulator_value);

Here,
accumulator: it is a variable and the first argument to be passed in the reduce method, it stores the calculated value up to the current index, and lastly when the processing of the whole array is finished, the value of the accumulator is what gets returned.
- we can pass the initial value of the accumulator in place of the accumulator_value variable.
- accumulator_value is an optional argument, and if we don't pass it, the reduce function will treat the first element of the array as the initial value

currentValue: it represents the current value that is getting executed at a particular instance of iteration.
currentIndex: it is again an optional argument, just like the initial value of the accumulator, we can pass it if we need the index of the elements inside our reduce function to apply the desired logic.
For instance, we need the index of the current element, if we wish to sum the elements present on the even indexes in the array.
array: the current array on which we're applying the reduce method, it's again an optional argument and can be used when such a need occurs.

You just need to take care of the sequence you're passing the arguments in the reduce function, as discussed above accumulator -> currentValue -> currentIndex -> array, this sequence will always hold.

Let's understand the different ways to use the reduce function using a simple example of calculating the sum of an array.

  1. Arrow Functions - here you would be defining an arrow function inside the filter function's argument.
const numbers = [2, 1, 3, 5, 4, 10];
const sum = numbers.reduce((sum, currentValue) => sum+currentValue, 0)
console.log(sum); // 25

In this arrow function, we're taking sum and currentValue as arguments, where sum represents our accumulator and currentValue represents the current value.

  1. Callback Function - just like other methods, here also you can make a separate callback function to write your logic. The value which this callback function will return will be the new accumulator value, and the argument that it takes is also in the same order which we've discussed above, i.e. accumulator -> currentValue -> currentIndex -> array.
const sum = (acc, curr) => acc+curr; 
const numbers = [2, 1, 3, 5, 4, 10];
const sumOfArray = numbers.reduce(sum);
console.log(sumOfArray); // 25

In this way of using reduce using a callback, we first defined a callback function sum() which is taking two arguments the acc which is the value of the accumulator and curr represents the current element. (It'll be in this order only, remember the sequence).
While passing this callback function inside reduce, it will automatically call the callback function in each iteration with the given arguments like acc, currentValue, etc. and store the returned value from that callback function as an updated value of the accumulator.

Example:

Let us take an example where we're given an array of objects, where each object represents the student's data (name, age and height (in cm)) and we need to apply reduce method over that array to find the tallest student, i.e. with the largest height.

const students = [
  { name: 'Kuldeep', age: 20, height: 165 },
  { name: 'Harshita', age: 25, height: 180 },
  { name: 'Taran', age: 24, height: 175 },
  { name: 'Gopal', age: 25, height: 185 },
];

const tallestStudent = students.reduce((tallest, currentStudent) => {
  if (currentStudent.height > tallest.height) {
    return currentStudent;
  } else {
    return tallest;
  }
});

console.log(tallestStudent.name); // Gopal

In this case, the accumulator is the tallest student found so far, and the current value is the current student being examined.

In the callback function, we compare the height of the current student with the height of the tallest student found so far. If the current student is taller, we return the current student as the new tallest student. Otherwise, we return the previous tallest student.
Finally, it is returning the final value which has been stored in our accumulator i.e. tallest variable. The final value will be an object as we are not passing any initial value, so by default the reduce function will take the first element as the initial value of tallest which is an object.
While printing we are using .[dot] operator to access the name property of our result object, so it's only printing the name and not the whole object.

Tip: you can use the ternary operator in place of if and else statements to make the code more concise and clean.

Where you might be needing reduce as an obvious choice —

  1. Calculating the sum of an array (as we've discussed above).

  2. Finding the maximum or minimum value in an array of numbers, or any extreme value in an array of any data type. (like in example we've discussed about finding the tallest student from an array of objects representing students)

     const numbers = [3, 7, 2, 9, 5];
     const max = numbers.reduce((acc, curr) => acc > curr ? acc : curr);
     console.log(max); // Output: 9
     //Here it will take 3 as an initial value of acc (accumulator)
    
  3. Flattening an array of arrays. (if you aren't getting it now, don't worry just practice some more questions on reduce and you'll get it eventually)

     const nestedArray = [[1, 2], [3, 4], [5, 6]];
     const flattenedArray = nestedArray.reduce((acc, curr) => acc.concat(curr), []);
     console.log(flattenedArray); // Output: [1, 2, 3, 4, 5, 6]
    

    Here, we're taking an empty array as an initial value and just concatenating values of our nested array in it, and finally returning our accumulator.

    Additional Remarks: You can use recursion if you want to flatten the array with multiple levels of nestedness (it's an advanced concept if you're a beginner then just ignore it)

  4. Removing Duplicates from an array. (here we would be using the .includes() method which just returns true if a particular element exists in our array)

     const arr = [1, 2, 3, 3, 1, 5, 3, 2, 4, "hi", "no", "hi", 2, 1];
    
     const uniqueArr = arr.reduce((acc, curr) => {
       if (acc.includes(curr) === false) {
         acc.push(curr);
       }
       return acc;
     }, []);
    
     console.log(uniqueArr); // Output: [1, 2, 3, 5, 4, "hi", "no"]
    

    Here, if a particular element is occurring first time so it will not be present in the accumulator array, for checking if it's present or not, we are using the .includes() method and if it's not present then the .include method is returning us false and if it's false then we are just pushing the current element into our accumulator array.
    Just like in the flattening of array example, here also we're taking an empty array as our initial value because we want our final result to be an array only, consisting of unique elements.
    Tip: you can use ternary operators to make the code more concise.

There are many such use cases where you would be using reduce method, also reduce takes more time in understanding and practice than map and filter, but at the same time reduce is the most powerful method which can be used flexibly if you know how to make use of it in a specific situation. The intuition to use reduce() comes with practice and experiencing the need for it in different scenarios.


Conclusion

Overall, map(), filter(), and reduce() are fundamental methods for working with arrays in JavaScript, and understanding how they work and when to use them can help you become a more effective JavaScript developer.

The map() method allows you to apply a function to each element of an array and create a new array with the results. This method is useful when you need to change the data's format or modify it by applying some function. It can be seen mostly in react when we need to use an array and show it over the website in a list format.

The filter() method allows you to create a new array that contains only the elements of the original array that meet certain conditions that you've specified as the logic in your callback or arrow function. This is useful when you need to extract specific data from an array or remove unwanted data, for example filtering those students scoring more than 75% avg. marks in the class

The reduce() method allows you to iterate over an array and accumulate a single value based on the elements in the array. This is useful when you need to perform a calculation on the data in an array or transform the data into a different format or reduce it to a single value by applying the logic.

By mastering these methods, you can write more concise and expressive code that is easier to read and maintain. However, it's important to keep in mind that these methods are not always the most optimized solution and may not be suitable for large arrays or complex operations.

You can read in-depth about these methods here: map, filter and reduce.


Never miss any article, follow my blog "Decoding the Code"


Do like, share and give your feedback!!

Happy Coding