Higher-Order Functions and Callbacks
Understand what makes a function 'higher-order,' how callbacks fit into that concept, and why treating functions as values is central to idiomatic JavaScript.
JavaScript treats functions as first-class values — they can be stored in variables, passed as arguments, and returned from other functions, just like a number or a string. Higher-order functions and callbacks are the direct consequence of that capability, and they are foundational to how idiomatic JavaScript is written.
Functions as first-class values
Calling functions "first-class values" means they can be treated exactly like any other kind of data: assigned to a variable, stored inside an array or object, passed as an argument into another function, or returned as the output of another function. No special syntax is needed — a function is just a value with a callable behavior attached.
This is the underlying feature that makes callbacks, higher-order functions, and later, closures, all possible in JavaScript.
What a callback is
A callback is a function passed into another function as an argument, with the expectation that the receiving function will invoke (call back) it at some appropriate point — immediately, later, repeatedly, or conditionally, depending on what the receiving function does.
Callbacks let you customize the behavior of a generic function without modifying its code: array.sort(compareFn) reuses the same sorting algorithm no matter what compareFn does, because the comparison logic is supplied externally as a callback.
What a higher-order function is
A higher-order function is any function that does at least one of two things: accepts another function as an argument, or returns a function as its result (or both). Functions that only work with plain data — numbers, strings, arrays of primitives — are not higher-order; the moment a function starts accepting or producing other functions, it qualifies.
Array methods like map, filter, reduce, forEach, and sort are all higher-order functions built into JavaScript — each one accepts a callback that defines exactly how it should process each element.
Functions returning functions
The other half of "higher-order" is a function that returns a new function. This pattern is often used to create specialized versions of a more general function — for example, a function multiplyBy(factor) that returns a new function which multiplies any number it is given by that fixed factor.
This pattern, where an inner function is returned and remembers variables from the outer function's scope, relies on closures — a topic that builds directly on the scope rules covered with regular functions and arrow functions.
Putting it together
The flow described in the outline — a plain function, passed into another function, becomes a callback the moment it is passed in, and the function that receives and invokes it is a higher-order function — captures the entire relationship in one sentence. Callback and higher-order function are two sides of the same interaction: one describes the function being passed in, the other describes the function doing the receiving and calling.
Higher-order function accepting and returning functions
repeat is higher-order because it accepts action as a callback and invokes it. multiplyBy is higher-order because it returns a brand-new function. double remembers factor = 2 from multiplyBy's scope even after multiplyBy has finished running — this is a closure.
// accepts a function (callback)
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, i => console.log("Run", i));
// returns a function
function multiplyBy(factor) {
return function (n) {
return n * factor;
};
}
const double = multiplyBy(2);
console.log(double(5));Code Examples
greetUser is a higher-order function because it accepts formatter as a parameter and calls it internally. shout is the callback — an arrow function passed in as a value, invoked inside greetUser rather than where it was defined. Expected result: ANA!!!
return formatter(name); is where the passed-in callback is actually invoked, with name as its argument.
filter, map, and reduce are all built-in higher-order functions, each accepting a callback (an arrow function) that defines exactly how it should behave. filter keeps only paid orders, map extracts each price, and reduce combines those prices into a single total — chained together without ever writing a manual loop. Expected result: 915 Each method returns a new array (or value), which is why they can be chained directly with dots — this chaining style is extremely common in real-world JavaScript data processing.
reduce((sum, price) => sum + price, 0) is itself a higher-order function call with a callback that accumulates a running total.
function greetUser(name, formatter) {
return formatter(name);
}
const shout = name => name.toUpperCase() + "!!!";
console.log(greetUser("ana", shout));const orders = [
{ item: "Book", price: 15, paid: true },
{ item: "Pen", price: 2, paid: false },
{ item: "Laptop", price: 900, paid: true }
];
const totalPaid = orders
.filter(order => order.paid)
.map(order => order.price)
.reduce((sum, price) => sum + price, 0);
console.log(totalPaid);Callback and higher-order function relationship
The same function is a plain function on its own; it becomes a 'callback' the moment it is handed to another function, and the function that receives and calls it is a 'higher-order function.'
function greet(name) { return "Hi " + name; }
│
│ passed as an argument
▼
greetUser("ana", greet)
│
▼
function greetUser(name, formatter) {
return formatter(name); ← greet is invoked HERE, as a callback
}
greet = the callback (the function being passed & called)
greetUser = the higher-order function (accepts & invokes it)Important Rules
A function is higher-order if it accepts a function as an argument, returns a function, or both.
A callback is simply a function passed as an argument, invoked by the function that received it — the receiving function controls when and how often it runs.
Built-in array methods like map, filter, reduce, forEach, and sort are all higher-order functions.
A function returning another function is a common pattern for creating specialized, reusable versions of general logic.
"Callback" and "higher-order function" describe the two sides of the same relationship, not two unrelated concepts.
Where this shows up
Virtually all modern JavaScript relies on this pattern: array.map/filter/reduce for data transformation, promise.then(callback) for async results, addEventListener("click", callback) for UI interaction, and framework code like React's onClick={handleClick} — all pass a function as a value into something else that will call it later.
numbers.forEach(sum += n);
numbers.forEach(n => { sum += n; });
forEach expects to be given a function to call for each element, not the result of an expression evaluated immediately — sum += n runs once, right away, and its result (not a function) is passed to forEach, which throws a TypeError because that value is not callable.
function multiplyBy(factor) { return n * factor; // n is not defined here } function multiplyBy(factor) { return function (n) { return n * factor; }; }
To make multiplyBy a true higher-order function that produces a reusable, specialized function, it must return a new function (which takes n later) rather than trying to compute a result immediately with a parameter, n, that was never declared.
[Multiple Choice] Which of these makes a function 'higher-order'? A. It only performs arithmetic on numbers B. It accepts a function as an argument, returns a function, or both C. It is declared using the arrow function syntax D. It has more than two parameters
B. It accepts a function as an argument, returns a function, or both — A higher-order function is defined purely by whether it takes a function in as an argument, hands one back as its return value, or does both — syntax style and parameter count are unrelated to the definition.
Write a higher-order function applyDiscount(percent) that returns a new function taking a price and returning the discounted price. Then use array.map with that returned function on the array [100, 200, 50].
function applyDiscount(percent) { return function (price) { return price * (1 - percent / 100); }; } const tenPercentOff = applyDiscount(10); const discounted = [100, 200, 50].map(tenPercentOff); console.log(discounted); // [90, 180, 45] applyDiscount should return an arrow function like price => price * (1 - percent / 100).