Higher-Order Functions
Functions are values in JavaScript. That single fact makes one of the most powerful patterns in the language possible: higher-order functions.
A higher-order function is any function that either:
- Takes one or more functions as arguments, or
- Returns a function as its result
Compare that to a first-order function, which only works with primitive values like numbers and strings and does neither.
// First-order: operates only on primitives
function add(a, b) {
return a + b;
}
add(2, 3); // 5
// Higher-order: takes a function as an argument
function operateOnNumbers(a, b, fn) {
return fn(a, b);
}
operateOnNumbers(2, 3, add); // 5
That operateOnNumbers function does not care how add works. It just delegates to whatever function you give it. That flexibility is the entire point.
Built-in Higher-Order Array Methods
JavaScript’s arrays come with higher-order functions built in. You have probably used them already without thinking of them as HOFs.
Transforming with map
map calls a function on every element and returns a new array of the results. The original array is untouched.
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2);
// [2, 4, 6, 8]
Selecting with filter
filter keeps only the elements where your callback returns true, and returns a new array.
const numbers = [1, 2, 3, 4];
const evens = numbers.filter(n => n % 2 === 0);
// [2, 4]
Accumulating with reduce
reduce processes every element and collapses them into a single value. You provide a callback and a starting value.
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((accumulator, n) => accumulator + n, 0);
// 10
reduce is the most flexible of the three. map and filter can both be expressed as reduce, but using them directly is cleaner.
Quick boolean checks with some and every
some returns true if at least one element passes the test. every returns true only if all elements pass.
const numbers = [1, 2, 3, 4];
numbers.some(n => n > 3); // true — 4 satisfies it
numbers.every(n => n > 0); // true — all are positive
These four methods — map, filter, reduce, some, every — cover the vast majority of array transformation tasks. Once you start thinking in terms of “transform this array” rather than “write a loop”, your code becomes shorter and easier to reason about.
Callbacks: Three Ways to Write Them
A callback is just a function you pass to another function. JavaScript gives you three common styles.
Anonymous function expression
numbers.filter(function(n) {
return n > 2;
});
Anonymous callbacks work fine, but when something goes wrong, your stack trace just says (anonymous function). That gets old fast in production code.
Named function
function isGreaterThanTwo(n) {
return n > 2;
}
numbers.filter(isGreaterThanTwo);
Named callbacks produce readable stack traces and are reusable. If you need the same filter logic in two places, a named function avoids duplication.
Arrow function
numbers.filter(n => n > 2);
Arrow functions are the default in modern JavaScript. They are concise and lexically bind this, which is what you want in most callback situations.
One caveat: if you are writing a callback inside a class method and that callback needs its own this, reach for a regular function expression instead. Arrow functions do not have their own this.
Closures: The Mechanism Behind Stateful HOFs
A closure is a function that captures variables from its enclosing lexical scope. Together with higher-order functions, closures enable some genuinely useful patterns.
The canonical example is a function factory:
function makeMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
double(5); // 10
triple(5); // 15
makeMultiplier returns a function. That returned function closes over factor — it remembers that value even after makeMultiplier has already returned. Call makeMultiplier twice with different arguments and you get two independent functions that each remember their own factor.
Closures also let you keep state private:
function makeCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
};
}
const counter = makeCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount(); // 2
The variable count is not accessible from the outside at all. Only the three methods returned by makeCounter can touch it. This is encapsulation without a class in sight.
This pattern — returning multiple functions that share private state — shows up constantly in JavaScript. Once you see it, you will start noticing it in middleware, event handlers, and module patterns.
Function Composition with pipe and compose
So far you have seen functions that take simple values and return simple values. Higher-order functions also let you combine functions together to build larger operations from smaller pieces.
The idea is straightforward: instead of nesting calls like this:
const result = round(toFixed(add(2, 3), 2));
You define a pipeline where data flows through each step in order.
function pipe(...fns) {
return function piped(value) {
return fns.reduce((v, fn) => fn(v), value);
};
}
const add = (a, b) => a + b;
const double = x => x * 2;
const round = x => Math.round(x);
const processNumber = pipe(add, double, round);
processNumber(2, 0); // 4
pipe(add, double, round) returns a new function. When you call that function with a value, it threads it through each function in sequence: add combines the value with a second argument, double scales the result, and round cleans it up.
compose does the same thing but in reverse order — it applies the rightmost function first. pipe reads more naturally for step-by-step data transformations because the order you write the functions matches the order they execute.
This is declarative programming. You say what should happen (add, then double, then round) rather than imperatively managing the intermediate values yourself.
Chaining Array Operations
The real payoff comes when you chain these methods together. Each method in the chain receives the output of the previous one, so you can build complex transformations without temporary variables.
const orders = [
{ id: 1, total: 100, status: 'shipped' },
{ id: 2, total: 250, status: 'pending' },
{ id: 3, total: 75, status: 'shipped' },
];
const totalRevenue = orders
.filter(o => o.status === 'shipped')
.map(o => o.total)
.reduce((sum, t) => sum + t, 0);
// 175
Each line has a single, focused job. The sequence reads almost like English: keep shipped orders, grab their totals, add them up.
This chain pattern is clean because each step produces exactly what the next step needs. No shared mutable state, no intermediate variables cluttering the scope.
See Also
- Immutability — Related FP concepts in the same series
- Functions and Scope — Closures and function fundamentals this article builds on