Skip to content

Higher Order Functions

You’ve learned how to write functions, pass them arguments, and return values from them. But functions in JavaScript can do something more interesting: they can work with other functions.

Remember map(), filter(), and forEach() from the Arrays article? You passed a function into each of them and they used it to do their job. Those are Higher Order Functions, and you’ve been using them all along without knowing the name.

This article goes beyond normal functions and shows you what makes a function “higher order”, and how to write your own.

Think about a coffee machine. You put in beans and milk, and it gives you coffee. That’s a function: it takes an input and produces an output.

Now think about a coffee machine factory. It takes an order, uses raw components to build a machine, and hands that machine over to you. The factory is a function too, but its output isn’t coffee. Its output is another machine, which is itself a function.

So both the coffee machine and the factory behave like functions, but the factory works one level higher. It produces something that can produce things.

The same idea works in the other direction too. Imagine a coffee machine that lets you plug in your own grinder. The machine doesn’t care how the grinder works, it just uses whatever you hand it. That’s a function that takes another function.

A coffee machine factory producing coffee machines, which in turn produce coffee

A higher order function (HOF) is a function that either takes a function as an argument or returns a function as output.

// Takes a function
function repeat(action) {
    action(); // calls the function the first time
    action(); // calls the function the second time
}

// Returns a function
function multiplyBy(x) {
    return function (y) {
        return x * y;
    };
}

repeat(() => console.log("Hello")); // outputs "Hello" twice

const double = multiplyBy(2);
console.log(double(5)); // outputs 10

In the above example you can see that:

  1. We have two higher order functions: repeat() and multiplyBy().
  2. The repeat() function takes the action() function and calls it two times.
  3. The multiplyBy(x) function takes a value x and returns a function that itself takes a value y and returns the multiplication result.
  4. We call repeat() and pass it a function that prints "Hello", so it prints twice.
  5. multiplyBy(2) returns a function, which we store in the variable double. Calling double(5) then runs that returned function with y as 5, giving 2 * 5.

A callback is a function that you pass as an argument to another function, usually a Higher Order Function.

const performAction = (action) => action(); // calls the callback function

const result = performAction(() => 2 + 5);
console.log(result); // outputs 7

// Anonymous callback function passed to the HOF
performAction(() => {
    console.log("Hi I am callback function"); // outputs "Hi I am callback function"
});

In the above example you can see that:

  1. We have a HOF performAction() that takes a callback function action.
  2. The action() function called inside performAction() can be anything or any operation.
  3. We passed a function that returns 2 + 5. performAction() returns that value, which we store in result and print.
  4. We reused performAction() with a different callback, this time one that logs a message.

Passing Arguments to the Callback Functions

Section titled “Passing Arguments to the Callback Functions”

Now that you’ve got the gist of HOFs, you might be wondering: how do we pass arguments to the callback itself?

Let’s look at an example:

const performAction = (action) => action();
const print = () => console.log("Hello world");

performAction(print); // outputs "Hello world"

In the above example, we created the print function and passed it as a callback to the performAction() HOF. However, the value in console.log is hardcoded. How can we pass any value to the print function itself?

const performAction = (action) => action();
const print = (data) => console.log(data);

performAction(() => print("Hello World!")); // outputs "Hello World!"
performAction(() => print(22)); // outputs 22

In the above example, we passed an anonymous callback function into performAction() that calls the print() function with the value we want to print.

const performAction = (action, ...args) => action(...args);
const print = (data) => console.log(data);

performAction(print, "Hello World!"); // outputs "Hello World!"
performAction(print, 22); // outputs 22

In the above example:

  1. We add another parameter to the HOF, a rest parameter ...args, and pass it into the callback as action(...args).
  2. Then we call performAction() with print as the first argument and the value to print as the second.

You’ve already used callbacks with map() and forEach(). In the next section, we’ll see how they work as Higher Order Functions.

If you remember, at the start of the article we saw that we have already used higher order functions when we learned about arrays. In this section, we will not only discuss them but also write these higher order functions from scratch.

The map() function iterates through each element in the array, transforms each one, and then returns a new array!

const fruits = ["apple", "banana", "mango"];
const capsFruits = fruits.map(fruit => fruit[0].toUpperCase() + fruit.slice(1));
console.log(capsFruits); // outputs ['Apple', 'Banana', 'Mango']
console.log(fruits); // outputs ['apple', 'banana', 'mango']

In the above example, we have a fruits array where each fruit has its name in all lowercase. We use map() to loop over each fruit in the array and capitalize the first letter.

Cool! I hope you haven’t forgotten this function. Now let’s make a few observations:

  1. The original fruits array is not modified by the map() function.
  2. The map() function accepted a callback function, and ran it on every item in the array.
  3. It applied the changes from the callback function to each item in the array.

Fine, now let’s write our own map() function:

function myMap(fn) {
    const result = [];
    for (let i = 0; i < this.length; i++) {
        const element = this[i];
        const res = fn(element);
        result.push(res);
    }
    return result;
}

Array.prototype.myMap = myMap; // assign this to the Array prototype object

const fruits = ["apple", "banana", "mango"];
const capsFruits = fruits.myMap(fruit => fruit[0].toUpperCase() + fruit.slice(1));
console.log(capsFruits); // outputs ['Apple', 'Banana', 'Mango']
console.log(fruits); // outputs ['apple', 'banana', 'mango']

In the above example, you can see that we created a myMap() HOF that takes a callback fn as an argument and then:

  1. Initializes an empty result array.
  2. Runs a loop on every element of the array and calls the fn callback on each element.
  3. Pushes each mapped element into the result array.
  4. Returns the result array.

Let’s see how this flows when we call fruits.myMap(...) with our capitalize callback:

How our myMap function works

The original fruits array is untouched because we only read from this and push the new values into a separate result array.

The forEach() function runs a callback once for every element in the array. Unlike map(), it doesn’t build a new array. It simply returns undefined, so it’s mostly used for side effects like printing values.

const fruits = ["apple", "banana", "mango"];
const result = fruits.forEach(fruit => console.log(fruit.toUpperCase()));
// outputs APPLE, BANANA and MANGO on separate lines
console.log(result); // outputs undefined

In the above example, we use forEach() to print every fruit in uppercase. Now let’s make a few observations:

  1. The forEach() function accepted a callback function, and ran it on every item in the array.
  2. It doesn’t return a new array. The value of result is undefined.
  3. The callback is only there to do some work for each item, not to produce a new value.

Now let’s write our own forEach() function:

function myForEach(fn) {
    for (let i = 0; i < this.length; i++) {
        const element = this[i];
        fn(element);
    }
}

Array.prototype.myForEach = myForEach; // assign this to the Array prototype object

const fruits = ["apple", "banana", "mango"];
fruits.myForEach(fruit => console.log(fruit.toUpperCase()));
// outputs APPLE, BANANA and MANGO on separate lines

In the above example, you can see that we created a myForEach() HOF that takes a callback fn as an argument and then:

  1. Runs a loop on every element of the array.
  2. Calls the fn callback on each element.
  3. Returns nothing. There’s no result array here, which is the main difference from myMap().

Let’s see how this flows when we call fruits.myForEach(...) with our printing callback:

How our myForEach function works

The filter() function runs a callback on every element and returns a new array containing only the elements for which the callback returned true.

const nums = [1, 2, 3, 4, 5, 6];
const evens = nums.filter(num => num % 2 === 0);
console.log(evens); // outputs [2, 4, 6]
console.log(nums); // outputs [1, 2, 3, 4, 5, 6]

In the above example, we have a nums array and we use filter() to keep only the even numbers. Now let’s make a few observations:

  1. The original nums array is not modified by the filter() function.
  2. The callback decides whether an item stays. It returns true to keep the item and false to drop it.
  3. The new array contains the original items, not the values returned by the callback. This is different from map().

Now let’s write our own filter() function:

function myFilter(fn) {
    const result = [];
    for (let i = 0; i < this.length; i++) {
        const element = this[i];
        if (fn(element)) {
            result.push(element);
        }
    }
    return result;
}

Array.prototype.myFilter = myFilter; // assign this to the Array prototype object

const nums = [1, 2, 3, 4, 5, 6];
const evens = nums.myFilter(num => num % 2 === 0);
console.log(evens); // outputs [2, 4, 6]
console.log(nums); // outputs [1, 2, 3, 4, 5, 6]

In the above example, you can see that we created a myFilter() HOF that takes a callback fn as an argument and then:

  1. Initializes an empty result array.
  2. Runs a loop on every element of the array and calls the fn callback on each element.
  3. If the callback returns true, pushes the original element into the result array.
  4. Returns the result array.

Let’s see how this flows when we call nums.myFilter(...) with our even number callback:

How our myFilter function works

Notice the difference from myMap(). There, we pushed the value returned by the callback. Here, the callback only answers yes or no, and we push the original element.

Now you know not only what Higher Order Functions are, but also how they behave in the array methods you already use: map(), forEach() and filter().

You learned how to:

  • Explain what a Higher Order Function is
  • Use callbacks, and pass arguments to them
  • Write your own map(), forEach() and filter() from scratch

Remember to pass the function itself, not the result of calling it. A Higher Order Function is just a function that works with other functions.