Lesson 69 of 80
Instead of nesting function calls inside each other until they're unreadable, composition chains them — the output of one becomes the input of the next, read in a straight line.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Composition means building a new function by feeding the output of one function straight into the input of the next. compose(f, g)(x) is f(g(x)) — you already do this by hand constantly, nesting calls like capitalize(trim(input)). Composition just gives that pattern a name and, more usefully, a reusable helper so you stop writing the nesting by hand once the chain gets past two or three functions.
The two common helpers differ only in reading direction. compose runs right-to-left, matching how nested calls read. pipe runs left-to-right, matching the order you'd actually describe the steps in conversation — "trim it, then lowercase it, then slugify it." Most real code reaches for pipe because that reading order matches how people narrate a process.
const pipe = (...fns) => (input) => fns.reduce((acc, fn) => fn(acc), input);
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const slugify = (s) => s.replace(/\s+/g, "-");
const toSlug = pipe(trim, lower, slugify);
toSlug(" Closures And Scope "); // "closures-and-scope"
pipe takes any number of single-argument functions and returns one new function. Calling that new function runs reduce over the list, threading the value through each step in order: trim runs first, its output feeds lower, that output feeds slugify. Nothing here is special syntax — pipe is nine lines of reduce, and the whole benefit is that toSlug's definition reads as a straight list of steps instead of slugify(lower(trim(s))), which gets unreadable fast once you're past three functions.
compose(f, g, h)(x) equals pipe(h, g, f)(x) — don't treat them as different concepts, just reverse the argument list to convert one into the other.slugify gets undefined because lower forgot a return, the error (or worse, a silent "undefined" string) shows up at the end of the chain, far from its real cause — this is the main debugging cost of composition and worth knowing going in..map().filter(). Method chaining calls methods on the same evolving object; pipe/compose chain independent, standalone functions that don't need to belong to anything or agree on a shared type beyond "one argument in, one value out."pipe is a higher-order function that returns a function built out of other functions — the exact same "function that takes or returns a function" idea behind debounce, memoize, and every function in this track that hands you back a new function instead of a plain value. Composition is what you get once you're comfortable treating functions as ordinary values you can pass around and combine.