Higher-Order Functions
Objectives
By the end of this chapter, you should be able to:
- Explain what a higher-order function is
- Write a function that takes another function as an argument and calls it
- Write a function that returns a new function
- Explain how a returned function connects back to closures
💡 Why this matters: In JavaScript, functions can be passed around just like numbers or strings: stored in variables, passed as arguments, returned from other functions. Once that clicks, a huge amount of JavaScript code (including the array methods you’ll meet next module) becomes far easier to read.
What Is a Higher-Order Function?
A higher-order function is a function that does at least one of these two things:
- takes another function as an argument, or
- returns a function.
In JavaScript, functions are values, so they can be passed into other functions and returned out of them, exactly like a number or a string could be.
Taking a Function as an Argument
Here’s a function repeat that calls whatever function it’s given, n times:
function repeat(n, fn) {
for (let i = 1; i <= n; i++) {
fn(i);
}
}
repeat(3, function (i) {
console.log("Rep " + i);
});
// Rep 1
// Rep 2
// Rep 3
repeat doesn’t know or care what fn actually does. It just calls it, three times, passing each call the current count. That’s what makes repeat reusable: pass it a different function, get completely different behavior, with no changes to repeat itself.
You can also pass in a function that already has a name, instead of writing one inline:
function sayHello(name) {
console.log("Hello, " + name);
}
function callTwice(fn, value) {
fn(value);
fn(value);
}
callTwice(sayHello, "Maya");
// Hello, Maya
// Hello, Maya
Notice sayHello is passed without parentheses, sayHello, not sayHello(). Writing sayHello() would call it immediately and pass its return value instead. Passing sayHello hands callTwice the function itself, to call whenever it wants.
Returning a Function
A higher-order function can also build and return a new function. This is where higher-order functions connect directly to closures from the last lesson.
function makeMultiplier(factor) {
return function (n) {
return n * factor;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5));
// 10
console.log(triple(5));
// 15
makeMultiplier returns a function, which makes makeMultiplier itself a higher-order function. But look closer at what it returns: a function that remembers factor from where it was created. That’s a closure, exactly like makeCounter in the previous lesson. double and triple are two separate closures, each remembering its own factor, which is why they behave differently even though they were built from the same code.
Try It
- Write a higher-order function
applyTwice(fn, value)that callsfnonvalue, then callsfnagain on the result of that first call, returning the final result. Write a small functionaddFive(n)that returnsn + 5. CallapplyTwice(addFive, 10)and log the result. - Write a higher-order function
makeAdder(x)that returns a function which addsxto whatever number it’s called with. Createadd10 = makeAdder(10), then calladd10(5)and log the result. - Using
makeAdderfrom the exercise above, create a second adder,add100, and confirm it behaves independently ofadd10by calling both and logging both results.
Recap
- A higher-order function either takes a function as an argument, returns a function, or both.
- Passing a function as an argument (without calling it) lets the receiving function decide when and how to call it.
- A function that returns another function is, in effect, returning a closure, one that remembers the variables from where it was built.
You’ll meet this pattern constantly starting next module: the built-in array methods .map(), .filter(), and .reduce() are all higher-order functions, each one takes a function as its argument, exactly like repeat() did here. Next lesson: exercises to practice everything from this module.