Here is the idea that unlocks most of modern JavaScript: functions are values. You can store one in a variable, hand it to another function as an argument, and get one back as a return value — exactly as you would a number or a string.
A higher-order function is simply a function that does one of those two things: it accepts a function as an argument, or it returns a function. You have already met several without the name being attached. map, filter, and reduce are higher-order because they take a callback. multiplyBy(3) is higher-order because it returns a new function. Once you recognise the pattern, an enormous amount of array code stops feeling like magic.
flowchart LR IN["input value"] --> H["higher-order<br/>function"] H --> OUT["new function"] style H fill:#e0900b,color:#fff
Accepting a function: map, filter, reduce
The three array methods you will use every day are all higher-order. Each takes a function and applies it for you, but they differ in what they give back.
map(fn) builds a brand-new array the same length as the original, with fn applied to each element. filter(fn) keeps only the elements for which fn returns something truthy, dropping the rest — the result is a subset, never longer than the input. reduce(fn, seed) folds the whole array down into a single value by carrying an accumulator from one element to the next.
The function you pass in has a narrow job: map's callback transforms one item, filter's callback decides keep-or-drop by returning a boolean, and reduce's callback combines the accumulator with the current item and returns the new accumulator.
const nums = [1, 2, 3, 4, 5, 6]; console.log(nums.map(n => n * 10)); // [10,20,30,40,50,60] console.log(nums.filter(n => n % 2 === 0)); // [2,4,6] console.log(nums.reduce((sum, n) => sum + n, 0)); // 21
flowchart LR A["array"] --> MAP["map(fn)"] --> NA["new array, same length"] A --> FILT["filter(fn)"] --> SUB["subset, only matches"] A --> RED["reduce(fn, seed)"] --> ONE["single value"] style NA fill:#1e7f3d,color:#fff style SUB fill:#b45309,color:#fff style ONE fill:#3776ab,color:#fff
Returning a function: closures and partial application
The other half of the pattern is a function that makes functions. multiplyBy(factor) does not multiply anything itself — it builds and returns a new function that remembers factor and multiplies by it later. The returned function has captured factor in a closure, exactly like the makeAdder you saw in the scope lesson.
This is called partial application: you supply some arguments now (the factor) and the rest later (the number to multiply). It is how you turn a general tool into a specific one. multiplyBy(3) gives you a dedicated tripler; multiplyBy(100) gives you a percent converter. One small function, many configured behaviours.
function multiplyBy(factor) {
return (n) => n * factor;
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
flowchart LR A["multiplyBy(3)"] -->|"returns"| F["a fn that multiplies by 3"] F -->|"call with 4"| R["12"] style F fill:#b45309,color:#fff style R fill:#1e7f3d,color:#fff
The classic trap: forEach returns undefined
Beginners reach for forEach when they mean map, and the bug is silent. forEach runs your callback on each element and then returns undefined — it builds nothing. So this looks reasonable and loses every result:
const result = words.forEach(w => w.toUpperCase()); // result is undefined
The callback dutifully uppercased each word and threw the value away, because forEach ignores what the callback returns. If you want a new array built from the results, you need map. The mnemonic: forEach is for side effects (logging, saving); map is for transforming into a new array. Mixing them up is the most common higher-order bug, and the one you will repair in the exercise below.
const words = ["hi", "yo"]; const r = words.forEach(w => w.toUpperCase()); console.log(r); // undefined — forEach returns nothing const built = words.map(w => w.toUpperCase()); console.log(built); // ["HI","YO"]
What does this print? multiplyBy returns a new function that closes over the factor.
function multiplyBy(factor) {
return (n) => n * factor;
}
const double = multiplyBy(2);
console.log(double(7));
multiplyBy(2) returns a function that multiplies its argument by 2.
double(7) calls that returned function with 7.
Write compose(f, g) so that compose(f, g)(x) returns f(g(x)). Both f and g are single-argument functions.
function compose(f, g) {
// return a new function
}
Return a function that takes x, calls g(x), then calls f on the result.
The order matters: g runs first, then f.
function compose(f, g) {
return function (x) {
return f(g(x));
};
}
This is meant to build and return a new array of uppercased words. It runs without error, but it returns undefined, because it uses forEach — which builds nothing. Repair it by switching to the array method that constructs and returns a new array from each callback's result.
function upperAll(words) {
const result = words.forEach(w => w.toUpperCase());
return result;
}
forEach returns undefined no matter what the callback returns.
map calls the callback and collects each result into a new array — that is the one you want.
function upperAll(words) {
const result = words.map(w => w.toUpperCase());
return result;
}
Write greaterThan(threshold) that returns a function. The returned function should take a number and return true when that number is greater than threshold. This is the transfer task: like multiplyBy, you return a configured function — but here it produces a boolean you can hand straight to filter.
function greaterThan(threshold) {
// return a function that compares its argument to threshold
}
Return an arrow n => n > threshold that closes over threshold.
filter keeps the items for which the callback is truthy, so greaterThan(4) keeps 6 and 8.
function greaterThan(threshold) {
return n => n > threshold;
}
What does this print? reduce starts the accumulator at the seed (100), then adds each element.
const nums = [1, 2, 3, 4]; console.log(nums.reduce((a, b) => a + b, 100));
The seed 100 is the starting accumulator.
Add 1, 2, 3, 4 to it: 100 + 1 + 2 + 3 + 4.
Recap
- A higher-order function accepts a function argument or returns a function (or both).
maptransforms into a new array,filterkeeps a subset,reducefolds to one value — always pass a seed to reduce.forEachreturnsundefined: use it for side effects, never to build an array. Reach formapwhen you need the results.- A function that returns a function uses a closure to bake in some arguments now and take the rest later (partial application).
Next you'll meet the older pattern JavaScript used for private state before classes — the IIFE and the module pattern — which leans on the very closures and scope rules you have just learned.