Higher Order Functions in JavaScript: Full Guide
A higher-order function takes or returns another function. Learn the concept, the built-in examples like map and filter, and how to write your own.
A higher-order function is any function that operates on other functions. It either takes a function as an argument, returns a function as its result, or does both. This is not a special keyword or syntax -- it is a natural pattern made possible by the fact that functions in JavaScript are first-class objects.
Here is the simplest possible higher-order function:
function callTwice(fn) {
fn();
fn();
}
callTwice(() => console.log("Hello!"));
// Hello!
// Hello!The callTwice function takes a function and calls it twice. It does not know or care what the function does. That separation, the higher-order function provides the structure while the passed function provides the behavior, is the core idea behind every example in this article.
The Two Forms of Higher-Order Functions
Both forms share the same principle: treating functions as values that can be passed around, stored, and returned.
Form 1: Functions That Take Functions
This is the more common form. You pass a function to another function to customize its behavior.
Built-in Examples
JavaScript's own runtime functions are full of this pattern already. setTimeout takes a callback and a delay, then calls the callback once the delay has passed, without needing to know anything about what that callback actually does:
setTimeout(() => {
console.log("Done!");
}, 1000);addEventListener works the same way, except it calls the handler function every time the matching event fires rather than just once:
button.addEventListener("click", () => {
console.log("Clicked!");
});Array methods like map, filter, and reduce are the most frequently used higher-order functions in everyday JavaScript code, and each one accepts a callback that describes exactly one step of the transformation:
const numbers = [1, 2, 3, 4, 5];
// map: transform every element
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter: keep elements that pass a test
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
// reduce: combine all elements into a single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15Each method takes a function that defines what to do. map asks "transform into what?" filter asks "keep or discard?" reduce asks "how should these combine?"
Writing Your Own
You can write higher-order functions that take callbacks just like the built-in ones, and this is often the cleanest way to separate a repeated process from the specific work done on each step:
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, (i) => console.log(`Iteration ${i}`));
// Iteration 0
// Iteration 1
// Iteration 2The repeat function handles the looping logic. The callback decides what happens on each iteration. This separation is the point: repeat does not need to know whether you are logging, updating the DOM, or calculating values, and the same repeat function works unchanged for all three.
Form 2: Functions That Return Functions
This pattern creates function factories, functions that build and configure other functions. Instead of writing a separate double and triple function by hand, one factory can produce as many variations as needed:
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15The createMultiplier function returns a new function that remembers the factor argument passed in when it was created. This is a closure in action: the returned function retains access to factor even after createMultiplier itself has finished running.
Practical Use: Configurable Functions
function createLogger(prefix) {
return function(message) {
console.log(`[${prefix}] ${message}`);
};
}
const info = createLogger("INFO");
const error = createLogger("ERROR");
info("Server started"); // [INFO] Server started
error("Connection lost"); // [ERROR] Connection lostOne factory function produces many configured loggers. Each one is a closure that remembers its own prefix.
Practical Use: Rate Limiter
A rate limiter is a good showcase for functions that return functions, since it needs to track state, like a running call count, across many calls without exposing that state to the outside world:
function createRateLimiter(maxCalls, windowMs) {
let calls = 0;
const reset = () => { calls = 0; };
setInterval(reset, windowMs);
return function() {
if (calls < maxCalls) {
calls++;
return true; // Allowed
}
return false; // Blocked
};
}Calling createRateLimiter sets up the counter and the reset timer once, then hands back a small function that checks and updates that counter on every call:
const limiter = createRateLimiter(3, 1000);
console.log(limiter()); // true
console.log(limiter()); // true
console.log(limiter()); // true
console.log(limiter()); // false (limit reached)The returned function remembers calls and maxCalls through the closure. Each call to the limiter checks and updates the count, and the interval timer resets it back to zero once the time window passes.
Chaining Higher-Order Functions
Since map, filter, and similar methods return arrays, you can chain them:
const orders = [
{ item: "Book", price: 12 },
{ item: "Pen", price: 2 },
{ item: "Notebook", price: 8 },
{ item: "Eraser", price: 1 },
];
const cheapItemNames = orders
.filter(order => order.price < 10)
.map(order => order.item.toUpperCase());
console.log(cheapItemNames); // ["PEN", "NOTEBOOK", "ERASER"]Each step is a single expression that says what you want, not how to do it. Compare with the equivalent loop:
const cheapItemNames = [];
for (let i = 0; i < orders.length; i++) {
if (orders[i].price < 10) {
cheapItemNames.push(orders[i].item.toUpperCase());
}
}The chained version reads closer to the intent: filter to cheap orders, then map to uppercase names.
For more on callbacks, see What Is a Callback Function in JS: Full Tutorial. For pure functions, see Pure vs Impure Functions in JavaScript Explained.
Rune AI
Key Insights
- A higher-order function takes a function as an argument, returns a function, or both.
- Built-in examples include map, filter, reduce, forEach, sort, and setTimeout.
- Functions that return functions are useful for configuration, partial application, and closures.
- Higher-order functions are the foundation of functional programming in JavaScript.
- Understanding them unlocks cleaner, more declarative code patterns.
Frequently Asked Questions
What makes a function 'higher-order'?
Are higher-order functions slower than loops?
Can I chain higher-order functions together?
Conclusion
Higher-order functions are not a special language feature. They are a natural consequence of functions being first-class objects in JavaScript. Once you understand that functions can be passed around like any other value, map, filter, reduce, and custom higher-order functions become intuitive tools for writing cleaner, more expressive code.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.