ES6 - Higher-Order Functions

Featured on daily.dev

Subscribe to my newsletter and never miss my upcoming articles

Introduction

In this article, we will first understand what a higher-order function is and then look at a few code examples of the popular higher-order functions such as forEach, map, filter, reduce, etc.

What are Higher Order Functions?

It is important to first understand what first-class citizens are before we move to higher-order functions.

First-Class Citizens

In JavaScript, functions are treated as 'First Class' citizens. The reason is that in JavaScript a function is fundamentally an object.

Let us take a quick example to explain this better. The below code is an example of a simple function.

//A simple function
function sayHello() {
    console.log('Hello');
}

//Invoke the function
sayHello(); //output -> Hello

When we say that Functions are objects, we can assign properties to the function similar to an object as shown below.

//Since the function is essentially an object, you can add properties to it
sayHello.greet = 'Namaste';

console.log(sayHello.greet); // Output -> Namaste

Note: While we can assign an attribute to a function. It is generally considered a bad practice. If you must add an attribute, then ensure you use an object.

You can also assign variables to a function as the code example shown below:

//A simple function
const sayHello = function() {
    console.log('Hello');
}

//Invoke the function
sayHello(); //output -> Hello

The above examples are that of 'First-Class' functions.

Higher-Order Functions

Higher-order functions are functions that take other functions as arguments or functions that return a function as their result.

The below code example will hopefully make the above explanation more clear.

//Function Sum of two arguments a & b
const sum = function (a, b) {
  return a + b;
};

//Higher order function - takes 'func' as an argument & returns a 'func' for execution
const higherOrderFunction = (func) => (a, b) => {
  return func(a, b);
};
const sumFor = higherOrderFunction(sum);

console.log(sumFor);

/**
 *  The Output of sumFor (HigherOrderFunction) is
 *  (a, b) => {
 *     return func(a, b);
 *  }
 * 
 */

console.log(sumFor(2, 3)); //Output -> 5

Things to note:

  • The function 'higherOrderFunction' accepts a function 'func' as a parameter.
  • The function 'func' that is passed in as a parameter is referred to as a callback.

Array.prototype.forEach, Array.prototype.map, Array.prototype.filter are few examples of high-order functions introduced with ES5.

Let's dive into the actual functions.

Array.forEach

The forEach function was introduced with ES5 as Array.prototype.forEach and with ES6, it's simply referenced as Array.forEach.

Syntax: Array.forEach( callback( currentValue, currentIndex, array));

The callback function receives three parameters:

  • The current element value - Mandatory
  • The current element index - Optional
  • The array that is being traversed.

Let us look at a code example:

//An array of Marvel Movies with the title & year of release
const marvelMovies = [
  {
    title: "Iron Man",
    year: 2008,
  },
  {
    title: "The Avengers",
    year: 2012,
  },
  {
    title: "Doctor Strange",
    year: 2016,
  },
  {
    title: "Avengers: Infinity War",
    year: 2018,
  },
  {
    title: "Avengers: End Game",
    year: 2019,
  },
];

//forEach loops through each movie & prints the name of the title & year it was released on the console
//The anonymous function accepts 'movie' as an argument which matches with the current element
marvelMovies.forEach((movie) => {
  console.log(`The title of the movie is $${movie.title}`);
  console.log(`${movie.year} is the year the movie was released.`);
});

/*
Output
---------
The title of the movie is $Iron Man
2008 is the year the movie was released.
The title of the movie is $The Avengers
2012 is the year the movie was released.
The title of the movie is $Doctor Strange
2016 is the year the movie was released.
The title of the movie is $Avengers: Infinity War
2018 is the year the movie was released.
The title of the movie is $Avengers: End Game
2019 is the year the movie was released.
---------
*/

Things to note:

  • The callback function is an anonymous function. It is used in conjunction with an arrow function. If you would like a refresher on arrow function, you can read over here.
  • I have used 'movie' to reference the current element that is passed in as the argument. In reality, you can use any name to the variable you are referencing the current element.
  • The forEach loops through the 'marvelMovies' array and displays the title and the year of release on the console.

Array.map

The map function was introduced with ES5 as Array.prototype.map and with ES6, it's simply referenced as Array.map.

Syntax: Array.map( callback( currentValue, currentIndex, array));

The callback function receives three parameters:

  • The current element value - Mandatory
  • The current element index - Optional
  • The array that is being traversed.

The important thing to note here is that the 'map' function returns a 'new' array with the results of the callback function applied to every element of the original array, in the same order.

Let us take the same marvelMovies array and use the map function to rate only the movies with the title 'Avengers'.

//An array of Marvel Movies with the title & year of release
const marvelMovies = [
  {
    title: "Iron Man",
    year: 2008,
  },
  {
    title: "The Avengers",
    year: 2012,
  },
  {
    title: "Doctor Strange",
    year: 2016,
  },
  {
    title: "Avengers: Infinity War",
    year: 2018,
  },
  {
    title: "Avengers: End Game",
    year: 2019,
  },
];

//map Function
//marvelMovies.map function will return a new array that'll be assigned to newMarvelMovies
const newMarvelMovies = marvelMovies.map((marvelMovie) => {

        //Check if the title contains 'Avengers'
    if(marvelMovie.title.includes('Avengers')) {
                //Add the 'rating' atrribute to the current element
        marvelMovie['rating'] = 5;
    }
        //the element will be returned as a part of the anonymous function
        //the element will be added as a part of the new array being returned from the map function
    return marvelMovie;
});

//Display the value of the new Array 'newMarvelMovies'
console.log(newMarvelMovies);

/*
Output ->
0: {title: "Iron Man", year: 2008}
1: {title: "The Avengers", year: 2012, rating: 5}
2: {title: "Doctor Strange", year: 2016}
3: {title: "Avengers: Infinity War", year: 2018, rating: 5}
4: {title: "Avengers: End Game", year: 2019, rating: 5}
*/

Things to Note:

  • The 'map' function returns a 'new' array.
  • In this example, I've used 'marvelMovie' name for the current element, just to demonstrate that any name can be used for referencing the current element.
  • The main use-case for a 'map' function is to edit or manipulate the elements of the array.

Array.filter

The filter function was introduced with ES5 as Array.prototype.filter and with ES6, it's simply referenced as an Array.filter.

Syntax: Array.filter( callback( currentValue, currentIndex, array));

The callback function receives three parameters:

  • The current element value - Mandatory
  • The current element index - Optional
  • The array that is being traversed.

The filter function creates a new array with all the elements that pass the condition check specified in the callback function.

Let us look at the following code example:

//An array of Marvel Movies with the title & year of release
const marvelMovies = [
  {
    title: "Iron Man",
    year: 2008,
  },
  {
    title: "The Avengers",
    year: 2012,
  },
  {
    title: "Doctor Strange",
    year: 2016,
  },
  {
    title: "Avengers: Infinity War",
    year: 2018,
  },
  {
    title: "Avengers: End Game",
    year: 2019,
  },
];

//filter Function
//filteredMarvelMovies is a new array
//marvelMovies.filter function will return only the elements who's title does not contain 'Avengers'
const filteredMarvelMovies = marvelMovies.filter((marvelMovie) => {
    return !marvelMovie.title.includes('Avengers');
});

//Display the value of the new Array 'filteredMarvelMovies'
console.log(filteredMarvelMovies);

/*
Output ->
0: {title: "Iron Man", year: 2008}
1: {title: "Doctor Strange", year: 2016}
*/

Things to Note:

  • The 'filter' function returns a 'new' array.
  • The main use-case for a 'filter' function is to extract a specific sub-set of elements of the array matching a specific condition.

Array.some

The 'some' function was introduced with ES5 as Array.prototype.filter and with ES6, it's simply referenced as Array.some.

Syntax: Array.some( callback( currentValue, currentIndex, array));

The callback function receives three parameters:

  • The current element value - Mandatory
  • The current element index - Optional
  • The array that is being traversed.

The 'some' function tests for whether at least one element in the array passes the test condition specified in the callback function. It returns a boolean value.

Let us look at the following code example:

//An array of Marvel Movies with the title & year of release
const marvelMovies = [
  {
    title: "Iron Man",
    year: 2008,
  },
  {
    title: "The Avengers",
    year: 2012,
  },
  {
    title: "Doctor Strange",
    year: 2016,
  },
  {
    title: "Avengers: Infinity War",
    year: 2018,
  },
  {
    title: "Avengers: End Game",
    year: 2019,
  },
];

//some function
//Checks & return a true if any one element title of the marvelMovies array 
//matches with the string 'Thor'
const checkTitle = marvelMovies.some(movie => {
    return movie.title === 'Thor'; 
});

//Display the boolean value returned from the 'some' function
console.log(checkTitle); //output -> false

I believe that the above code with the comments above is self-explanatory. Basically, the 'some' function checks if any one of the values in the array matches the condition (title matching 'Thor') and returns the value 'false', since there's no matching value in the marvelMovies array.

Array.every

The 'every' function was introduced with ES5 as Array.prototype.every and with ES6, it's simply referenced as Array.every.

Syntax: Array.every( callback( currentValue, currentIndex, array));

The callback function receives three parameters:

  • The current element value - Mandatory
  • The current element index - Optional
  • The array that is being traversed.

The 'every' function tests whether all the elements in the array pass the condition specified in the callback function. It returns a boolean value.

Let us look at the code example below:

//An array of Marvel Movies with the title, year of release & rating
const marvelMovies = [
  {
    title: "Iron Man",
    year: 2008,
    rating: 5
  },
  {
    title: "The Avengers",
    year: 2012,
    rating: 5
  },
  {
    title: "Doctor Strange",
    year: 2016,
    rating: 4
  },
  {
    title: "Avengers: Infinity War",
    year: 2018,
    rating: 5
  },
  {
    title: "Avengers: End Game",
    year: 2019,
    rating: 5
  },
];

//every function
//Checks & returns a 'true' if all of elements in the marvelMovies array have a rating above 3
const checkRating = marvelMovies.every(movie => {
    return movie.rating > 3; 
});

//Display the boolean value returned from the 'every' function
console.log(checkRating); //output -> true

Array.find & Array.findIndex

The find & findIndex were introduced with ES5 as Array.prototype.find & Array.prototype.findIndex. With ES6, they can be used as Array.find & Array.findIndex.

The Array.find function returns the first matching element based on the condition specified in the callback function.

The Array.findIndex function returns the index of the first matching element based on the condition specified in the callback function.

The code example below gives a quick example of how to use find & findIndex functions.

//An array of Marvel Movies with the title, year of release & rating
const marvelMovies = [
  {
    title: "Iron Man",
    year: 2008,
    rating: 4
  },
  {
    title: "The Avengers",
    year: 2012,
    rating: 3
  },
  {
    title: "Doctor Strange",
    year: 2016,
    rating: 4
  },
  {
    title: "Avengers: Infinity War",
    year: 2018,
    rating: 5
  },
  {
    title: "Avengers: End Game",
    year: 2019,
    rating: 5
  },
];

//find function
//Checks & returns the first element that matches with the condition 'rating' equals 5
const found = marvelMovies.find(movie => {
    return movie.rating === 5; 
});

//Display the value of 'found' in the Console
console.log(found); 

//output -> {title: "Avengers: Infinity War", year: 2018, rating: 5}
//Even though the movie 'Avengers: End Game' also contains the rating 5,
//the first matching element is returned

--------------------------------------------------------------------------

//findIndex function
//Checks & returns the first element that matches with the condition 'rating' equals 4

const foundIndex = marvelMovies.findIndex(movie => {
    return movie.rating === 4; 
});

//Display the value of 'foundIndex' in the Console
console.log(foundIndex); //Output -> 0 (Array index starts from 0)
//The first matching element with a rating 4 is present in Array Index location 0

Array.reduce

The 'reduce' function was introduced with ES5 as Array.prototype.reduce. With ES6, it's simplified as Array.reduce.

Syntax: Array.reduce( callback( accumulator, currentValue, currentIndex, array));

The callback function receives four parameters:

  • The accumulator - Mandatory
  • The current element value - Mandatory
  • The current element index - Optional
  • The array that is being traversed.

The reducer function executes the callback function specified over each element of the array to finally return a single value.

In simplified words, through each iteration, the value returned by the callback function is assigned to the 'accumulator' and ultimately becomes the final single resulting value.

The reduce function is the one that's most often not well understood. Hence, I'll take an example without reducer and then use a reducer to explain the code flow.

//The marvelMovies array contains the title & profit each movie has made
const marvelMovies = [
    {
      title: "Iron Man",
      profit: 100000
    },
    {
      title: "The Avengers",
      profit: 200000
    },
    {
      title: "Doctor Strange",
      profit: 200000
    },
];    

//Initialize the totalProfit variable
let totalProfit = 0;

//Use forEach function to loop through the iteration & add the profit of each movie
//to the totalProfit variable
marvelMovies.forEach(movie => {
    totalProfit += movie.profit;
});

//Display the value of totalProfit on console
console.log(totalProfit);

Let us see how the above code can be simplified using the reduce function.

//The marvelMovies array contains the title & profit each movie has made
const marvelMovies = [
    {
      title: "Iron Man",
      profit: 100000
    },
    {
      title: "The Avengers",
      profit: 200000
    },
    {
      title: "Doctor Strange",
      profit: 200000
    },
];

//Use reduce function to loop through the iteration & add the movie's profit
//to the accumulator & return it to the 'totalProfit' variable
const totalProfit = marvelMovies.reduce((accumulator, movie) => {
    return accumulator + movie.profit;
}, 0);

//Display the value of totalProfit on console
console.log(totalProfit);

Things to note:

  • The 'reduce' function takes in an 'accumulator' variable which holds the return value after every iteration.
  • In the example, the movie's profit is added to the 'accumulator' exactly as how 'total' did in the previous example.
  • The main use case of a reduce function is whenever any computation in an array needs to result in a singular value.

Conclusion

To summarize, we started with understanding what higher-order functions are and then saw a few code examples for functions such as forEach, map, filter, reduce, same & every.

The important thing to remember is that both the functions map & filter return 'new' arrays.

The 'map' function is primarily used to manipulate the values of elements of the array based on a condition, whereas, a filter function is used to generally filter out few elements from the array based on a condition. They are very heavily used in actual real-world applications especially with the React framework.

I hope you enjoyed this article. Don't forget to connect with me on Twitter @skaytech.

You will also enjoy the following articles:

Bob Jones's photo

Hi Skay, thanks for the article.

There is an incorrect comment in your code:

//Higher order function - takes 'func' as an argument & returns a 'func' for execution
const higherOrderFunction = function(func, a, b) {
    return func(a, b);
}

This function is not actually returning a function, but rather the result of the callback function, which is data.

A more concise example might be:

const sum = function(a, b) {
    return a + b;
}
//Higher order function - takes 'func' as an argument & returns a 'func' for execution
const higherOrderFunction = (func) => (a, b) => {
    return func(a, b);
}
const sumFor = higherOrderFunction(sum);
console.log(sumFor(2,3));

Here, higherOrderFunction takes func and returns a function, which then takes a,b and return data.

Cheers!

Skay's photo

Bob Jones - Thank you for catching this! While I had intended to convey what you had indicated it, I did not want to use arrow functions and it got a little messy.

I have updated the main section of code, with your code snippet and included additional comments on what the higher-order function returns.

Once again, thank you for catching this. Really appreciate it!!

Luciana Murimi's photo

Hi Skay Great article. Simple and concise.

Just a typo that I caught under Array.reduce syntax section:

Syntax: Array.every( callback( accumulator, currentValue, currentIndex, array)); -> theres .every instead of .reduce

Skay's photo

Luciana Murimi - Thanks for catching it!! Fixed it :-) Do check out my other articles as well :-)

Ruslan Zubenko's photo

Thank you for a very clear and concise article!

Skay's photo

Thanks for reading ❤️

Nimrod Kramer's photo

Congrats on being Featured on daily.dev! The badge looks great on your post :) Skay

Skay's photo

Thanks for the love Nimrod Kramer :-)

Ahmed Altaai's photo

Thorough explanation and very refreshing which will help me in the interview process!

Well done!

Skay's photo

Comments like these really make it worthwhile the effort... thank you for your kind words... Do share for maximum reach :-)

Jome Favourite's photo

Hi, the Array.find & Array.findIndex example, I didn't understand it clearly, so I decided to run it and I got a different output.

Skay's photo

Jome Favourite - Please accept my apologies. I have corrected the code snippet. Kindly verify the same and let me know if you still do not understand it.

Jome Favourite's photo

It's clear now, no need to apologise I'm still a newbie 😊. But accepted. It the same now and I understand too Skay