Higher Order Functions in JavaScript

Introduction to map( ), filter( ) and reduce( ) methods

Higher Order Functions in JavaScript

Photo by Ben Allan on Unsplash

In JavaScript, functions are considered First-Class Citizens. So what is a first-class function?

First-Class functions can be...

  • assigned to a variable
  • passed as an argument to another function
  • returned as a result from another function.

Basically, first-class functions are similar in this respect to variables in JavaScript. Let's take a look at this in code:

Store a reference to a function in a variable

let add = function(a, b) {
     return a + b
}

let sum = add(5, 10);   // sum = 15

Pass a Function as an Argument to another Function

function average(a, b, fn) {
    return fn(a, b) / 2;
}

let result = average(10, 20, add);
console.log(result);    // output -> 15

Return a Function from another Function

function returnFunction() {
     return function hello() {
                console.log("Hello There");
     };
}
// take note of how we are calling the function
returnFunction()();
// OR save the function to a variable and invoke the variable as a function 
let callHello = returnFunction();
callHello();

Note how we have to invoke/call the above function. If we only call returnFunction() the inner function definition of hello() is returned. To make sure the inner function is also executed we add the second set of parentheses.

We learn about first-class functions first, so we now have a little background and we can start understanding higher-order functions.

Higher Order Functions

When I was researching to write this article this was the best description that I came across to explain higher order functions. Higher Orders Functions are functions that perform operations on other functions.

OK, thats great from an abstraction point of view, but what about laymen's terms? Higher-Order functions are functions that take other functions as arguments or they return other functions.

Better, but still sounds confusing from a beginner perspective I think. So let's take a look at some of JavaScripts built-in higher-order functions to see how they function and help us write cleaner and often times shorter code.

forEach

If you have been following my articles then we have seen a higher-order function already.

let arr = [1, 2, 3, 4, 5];

let times2Arr = arr.forEach(function(element) {
     element * 2;
});

// times2Arr -> [2, 4, 6, 8, 10]

The reason the forEach() method and the following methods qualify as higher-order functions is because it takes in a callback function as an input argument. Each item in the array is taken one at a time in order and applied to the logic within the callback function.

There are plenty of other higher-order functions that are built-in to JavaScript: find(), sort(), etc... that you may have used or come across in your programming studies already. Today, however, we are going to focus on three of the most commonly used higher-order functions map( ), filter( ) and reduce( ).

Map

How does a map() function work and what does it do?

The map function will create a new Array by calling the callback function provided and run each element/item through the callback function logic. The newly created array that is returned should be stored in a new variable so we can use it later in our code.

let arr = [1, 2, 3, 4, 5];

let timesArr = arr.map(function(element) {
     element * 2;
});

// timesArr -> [2, 4, 6, 8, 10] 

let arrowArr = arr.map( (element) => element ** 2 );

Look's pretty similar to the way a forEach() method works, doesn't it? Let's walk through what is happening in the code above.

We start by defining a variable to hold the new Array that is created and returned by the map() method. Next we provide the Array object that we want to perform an operation on followed by a dot(.) and the map() method keyword. Within the parentheses of our map() method is the callback function (or operation) that we want performed on each item from the original array. Very much like the forEach() method that we are familiar with, we define the callback function with a parameter "element" that is used as an iterator. One way that you can think of this is by taking each item from the array one-by-one, in order from zero index to the end of the array, and performing the logic upon each item. What is the logic we have in our code? We are just taking the element and multiplying it by 2. So at every iteration through this loop, each new value that results from running it through the callback function logic, is added to the new Array that is created and then returned to the variable we defined, in this case 'timesArr'.

Using with Arrow Functions

We can also see where these methods really shine when we combine them with arrow functions. Using arrow functions with these methods also helps make the code we write clean, short, and concise!

let arr = [1, 2, 3, 4, 5];

let timesArr = arr.map(function(element) {
     element * 2;
});

// output -> [2, 4, 6, 8, 10] 

let arrowArr = arr.map(element => element * 2);

Filter

You can think of filter() as a for loop, that is specifically for filtering in or out certain values from an array. How does the filter method determine what should or should not be included?

Just like the map() method, filter() creates a new array with the values that pass the condition within the callback function.

let arr = [1, 2, 3, 4, 5];

let filteredArr = arr.filter((element) => {
     element % 2 == 0;
});

// filteredArr -> [2, 4];

Let's walk through what is happening in the code above. Again we have a variable to hold the returned array and we start with array object that we want to run the filter method on. Now the callback function that we define is going to take each element in the array and test to see if it is an even value. IF the logic evaluates to true, then we keep that value and add it to the array that gets returned at the end. IF the condition evaluates to false then we do not include that element in the new array and we move onto the next iteration and evaluate the next value from the array.

Reduce

When using the map() and filter() methods a new array is created and returned. With the next method, reduce(), the callback function that we define will be executed on each item from the array and return a single value at the end of the loop.

The reduce method accepts two parameters: The reducer function (callback), and an optional initialValue.

Array.reduce(function(callback) => {  /* callback logic  */ }, initalValue);

Example with no initalValue provided

let arr = [1, 2, 3, 4, 5];

const total = arr.reduce((sum, currentValue) => {
  return sum + currentValue;
});

// total -> 15

The first parameter is an accumulator and the second parameter is an element from the numbers array. The accumulator parameter (sum in the example above) keeps track of the total through each iteration as reduce() applies the callback function logic to each element of the array.

Example with an initalValue provided

const totalWithInitalVal = arr.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, 10);

// totalWithInitalVal -> 25
  • If we provide an initialValue, then the accumulator will be equal to the initialValue and the currentValue will be equal to the first element in the array.
  • If no initialValue is provided, then the accumulator will be equal to the first element in the array and the currentValue will be equal to the second element in the array.

The reduce() function can be confusing at first, but keep practicing and you will soon feel comfortable with it. This is also a function that will help us out a lot when we start learning about React. So practice, practice, practice!

Function Chaining with Higher-Order Functions

By using method chaining we have the ability of combining several operations into a single line. This is sometimes what people refer to as clean or elegant code when programming. Really once you understand how each function works under the hood individually, using chaining operations can make it so we have less code that we have to write to get the same results.

let given = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let result = given.map(elem => elem + 2).filter(elem => elem % 2 == 0).reduce((elem, sum) => sum + elem);

// new map() array  -> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
// filtered array  ->       [4, 6, 8, 10, 12]
// reduced sum ->        result = 40

There is more to learn about Higher-Order Functions and the methods that we covered here. We got a good start on understanding what is happening under the hood with these functions and how they can help us write cleaner, easier-to-debug code. But there is still more depth to these and we will talk about them and how you can create your own higher-order functions in future articles.

I hope this article on Higher-Order Functions in JavaScript was useful to you. Thanks for reading, until next time!