Implement compose() & pipe() — Function Pipelines

compose(f, g, h)(x) runs right-to-left; pipe(f, g, h)(x) runs left-to-right. Implement both with reduceRight/reduce, handle multiple arguments for the first function, and build an async pipe variant.

10 min read
JavaScript
Interview
Implementation
Functional

TABLE OF CONTENTS

Compose and pipe are two sides of the same functional programming coin. The base implementations are short — but interviewers push further with async variants, error propagation, and the ability to explain why one direction is idiomatic over the other.


What are compose() and pipe()?

compose(f, g, h) returns a new function that applies h first, then g, then fright-to-left. This mirrors mathematical function composition: f(g(h(x))).

pipe(f, g, h) is the same idea but left-to-right: apply f first, then g, then h. This reads like a Unix pipeline or a sequence of transformations — more natural for most developers.

Both are at their core a reduce over an array of functions — each function receives the output of the previous one and passes its result to the next.

Real-world use cases:

  • Data transformation pipelines — parse → validate → normalize → format as separate composable steps
  • Redux middlewareapplyMiddleware uses compose to chain store enhancers
  • Selector composition — deriving computed values from state by composing selectors
  • Functional utility libraries — Ramda, Lodash/FP, and RxJS pipelines are built on this pattern

The interview escalates in two directions. First, the multi-argument first function: the initial function in the chain can take multiple arguments; only its return value is passed forward. Second, async compose/pipe: when any function in the chain returns a Promise, the whole pipeline must become async — you replace reduce with an async loop using await.


The Problem

"Implement compose(...fns) that takes any number of functions and returns a new function. When called, it applies the functions right-to-left, passing the result of each as input to the next."

The interviewer extends:

"Now implement pipe(...fns) — same idea but left-to-right. Then make pipeAsync that handles functions returning Promises."


Thought Process

Both functions share the same structure: a reducer over an array of functions.

  • compose uses reduceRight — start from the last function, work leftward
  • pipe uses reduce — start from the first function, work rightward

For the multi-argument case: the first function in the execution order (rightmost in compose, leftmost in pipe) is called with all the initial arguments via rest params. Every subsequent function receives exactly one argument — the result of the previous call.

For async: replace the synchronous reduce loop with an async loop. Each step awaits the result before passing it to the next function. This handles both sync and async functions in the same pipeline.


Step 1 — compose(): Right-to-Left

Loading editor...


Step 2 — Cleaner compose() with reduceRight

The standard idiom avoids the index check by initializing the accumulator with the first call:

Loading editor...


Step 3 — pipe(): Left-to-Right

pipe is compose with the array processed in forward order:

Loading editor...


Step 4 — pipeAsync(): Async Pipeline

When any function returns a Promise, chain with await:

Loading editor...


Full Solution

Loading editor...


What Interviewers Are Testing

  • reduce vs reduceRight — understanding why compose uses reduceRight (or reversing + reduce)
  • Multi-arg first function — knowing only the entry point handles spread args; all subsequent functions take one
  • Async awareness — replacing reduce with a for...of + await loop (not reduce with async callbacks, which breaks)
  • Symmetry of compose/pipe — explaining that they're identical except for the direction of traversal
  • Edge cases — empty fns, single fns (identity / passthrough)

Interview Tips

  • Start with pipe — it reads left-to-right like English and is easier to explain. Then show compose as "the same thing, reversed."
  • Don't use reduce for pipeAsyncreduce with async callbacks doesn't actually await each step; you need a for...of loop or reduce with promise.then() chaining. Mention this explicitly — it's a common trap.
  • Relate to something real — "Redux's applyMiddleware uses compose to chain enhancers" shows you've seen this in production code.
  • Name the FP concept — "This is function composition — the output of one function becomes the input of the next. It's how you build pipelines of pure transformations."

Related Questions


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI