Higher-order functions and composition: building pipelines from small functions
Functions that take or return functions power map, filter, reduce, middleware and React hooks. Learn to compose small steps with pipe and compose.
Comes up in: JavaScript, Coding
Learn#
Karthik’s product page has a 40-line function that cleans a title: trims it, lowercases it, removes symbols, cuts it to length. Every new rule makes it longer and harder to test. He needs small steps he can line up.
Functions in JavaScript are values: you can pass them in and return them. A function that does either is a higher-order function. You already use them: map, filter, reduce, addEventListener, setTimeout.
A function can also build and return a new function, like the pipe below.
Composition joins small functions into one: the output of each becomes the input of the next. pipe(a, b, c) runs left to right; compose(c, b, a) does the same right to left, like maths.
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const slug = (s) => s.replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const pipe = (...fns) => (x) => fns.reduce((value, f) => f(value), x);
const toSlug = pipe(trim, lower, slug);
console.log(toSlug(" Hello World, India! "));
console.log([3, 1, 2].map((n) => n * 2).filter((n) => n > 2), [1, 2, 3].reduce((a, b) => a + b, 0));Output (verified)
hello-world-india [ 6, 4 ] 6
Writing pipe(trim, lower) without naming the argument is called point-free style. It reads well for short chains; for long or branching logic, a plain function with names is clearer.
pipeline
- trim
- lower
- slug
value
- ' Hello World, India! '
toSlug receives ' Hello World, India! '. pipe passes it to the first function.
Read the steps as text
- toSlug receives ' Hello World, India! '. pipe passes it to the first function.
- trim removes the spaces at both ends.
- lower's input is trim's output.
- slug replaces runs of other characters with dashes and trims dashes. Done.
Predict#
Predict first
What does this print?
const add1 = (x) => x + 1;
const double = (x) => x * 2;
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
console.log(pipe(add1, double)(3));pipe runs left to right: add1(3) is 4, then double(4) is 8. If you answered 7, you applied double first — that would be compose(add1, double).
Interview angle#
Say it like this — about 30 seconds
“A higher-order function takes a function as an argument or returns one. The standard library is full of them:
map,filter,reduce,sortwith a comparator,addEventListener,setTimeout,Promise.then. Returning functions gives you factories likedebounceoronce. Composition chains small functions so each output feeds the next:pipeleft to right,composeright to left. It keeps steps small and testable, but I stop when it hurts readability.”
Follow-up Give examples of higher-order functions in the standard library.
Array.prototype.map, filter, reduce, find, sort; Promise.prototype.then; addEventListener; setTimeout; Function.prototype.bind returns one.Follow-up How would you implement pipe?
(...fns) => (x) => fns.reduce((v, f) => f(v), x) — and compose uses reduceRight.Follow-up Where does composition appear in real frameworks?
pipe operators.Practice#
On your own
Write pipe and compose with reduce and reduceRight.
Done when
pipe(add1, double)(3)is 8 andcompose(add1, double)(3)is 7.- With no functions, both return the input unchanged.
- Each function is called once per call.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What is a higher-order function?
A function that takes a function as an argument or returns one.
In which order does pipe(f, g, h) run?
Left to right: h(g(f(x))).
How does compose differ from pipe?
compose runs right to left.
Name three higher-order functions on arrays.
map, filter, reduce (also find, some, sort).
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
Pass behavior in, get behavior out. Small single-purpose functions plus pipe make data flows easy to read and test.
Check yourself: Is Array.prototype.map a higher-order function?
Yes. It takes a function as an argument.
How it works underneathSenior rounds
pipe is one reduce: the accumulator starts as the input, and each step replaces it with f(accumulator) Source: MDN: Array.prototype.reduce(). Each returned function is a closure over fns (JS.02.03), which is why pipe can return a reusable pipeline.
Check yourself: What value does reduce start with in pipe?
The input x — the initial accumulator.
Edge cases and trapsWhere people slip
A synchronous pipe passes promises along unchanged, so async steps need an async version: (x) => fns.reduce(async (v, f) => f(await v), x). Functions with more than one argument need currying first (JS.07.02). The pipeline operator |> is still a proposal, not part of the language Source: TC39: Pipeline operator proposal.
Check yourself: Can pipe handle async functions as written?
No. It would pass a promise to the next step; an async pipe must await each step.
In productionStaff-level answers
Name your steps, keep chains short, and add a tap((x) => console.log(x)) step while debugging. In data-heavy code, prefer a clear for...of loop over long chains of map/filter that walk the array many times.
Check yourself: Why is a long point-free chain hard to debug?
There are no named intermediate values to inspect, and stack traces show anonymous steps.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- What is a higher-order function? Give examples from the standard library.