What is a higher-order function?

Functions in JavaScript are first-class, meaning that a function can be assigned as a value to a variable, passed as an argument or returned by another function.

// assigning function to a variable
const fullnameExampleOne = (firstname, lastname) => {
  return `${firstname} ${lastname}`;
}

fullnameExampleOne('First', 'Last'); // returns 'First Last'

// passing function as an argument
const fullnameExampleTwo = (firstname, lastname) => {
  return `${firstname} ${lastname}`;
}

function getNameTwo(getFullnameExampleTwo) {
  console.log(getFullnameExampleTwo('First', 'Last'));
}

getNameTwo(fullnameExampleTwo); // returns 'First Last'

// returning a function
const fullnameExampleThree = (firstname, lastname) => {
  return `${firstname} ${lastname}`;
}

const getNameThree = (getFullnameExampleThree) => {
  return getFullnameExampleThree;
}

getNameThree(fullnameExampleThree)('First Last');

A function that either takes another function as an argument or returns a function is called a higher-order function. Higher-order functions are everywhere in JavaScript and is one of the most important things to understand when learning it.

We can simply pass a function as an argument to another function and execute it after all of the actions in the main function have been completed. This is especially useful when actions inside the function are slow. This concept is called a callback.

const fs = require('fs);

fs.readFile('/some/big/file', (err, data) => {
  if (err) {
    throw err;
  }

  return 'Success';
});

In the above example we are using Node’s fs (file system) module to read a big file and let us know if there's been an error. The second argument is a callback that gets executed upon failure or completion of the operation. So Node can just let the process run and carry on with other tasks. It will inform us via the callback when the it’s done dealing with the file. Nice.

Let’s take a look at another example: event listener.

document.querySelector('button').addEventListener('click', (e) => {
  alert('I have been clicked');
});

We pass an anonymous function to addEventListener() method and display an alert. We could refactor this to make the anonymous function reusable. Let’s extract an anonymous function, assign it to a variable showAlert and pass to the event listener.

const showAlert = () => {
  alert('I have been clicked');
};

document.querySelector('button').addEventListener('click', showAlert);

Note that we are not executing showAlert, we are just passing it as a function object. showAlert is a separate function now that can be reused.

One more example. Let’s say there is an array of cars and we need to filter by colour.

const cars = [
    { make: 'Toyota', model: 'Camri', colour: 'blue' },
    { make: 'Volkswagen', model: 'Golf', colour: 'red' },
    { make: 'BMW', model: 'M3', colour: 'red' },
    { make: 'Ford', model: 'Focus', colour: 'white' }
];

We can use the native filter() method which returns a new array of elements. It iterates over the array, passes each element into the callback, checks whether the element satisfies the checks and depending on the outcome pushes the element into the new array.

cars.filter((car) => {
    return car.colour === 'red;
});

filter() is a higher-order function and it accepts a function as its argument. There are many other native functions which we can call on the Array object such as map(), reduce() and more.

With just a minor refactor, you codebase will benefit from these functional programming concepts immensely. Function composition is one the benefits of using higher-order functions. We can compose bigger functions with smaller functions, thus making our code cleaner, easier to test and debug. It is also easier to reuse smaller functions as they should only serve a single purpose.