skip to content

Compose vs Pipe Direction

The same wiring written two ways, and the confusion when a codebase mixes them: reading order and application order disagree. Interviewers ask which way an operator runs and why the team picked it.

on this pageshow

questions

4

In a right-to-left composition written compose(compress, watermark, resize), which stage sees the raw photo first?

level: juniorimportance: must knowfreq 64%

answer

  1. same wiring, two listings
  2. listing order is a convention
  3. reading order versus run order
  4. rightmost stage touches the argument
  5. compose mirrors nested calls

basics

~10 s

Resize sees it first. A right-to-left combinator applies the last-listed function to the argument and feeds each result leftwards, so the stages are listed in the reverse of the order they run.

solid answer

~30 s

A right-to-left `compose` hands its argument to the **rightmost** function, then passes that result to the one on its left, and so on. So `compose(compress, watermark, resize)` resizes the photo, watermarks the resized image, and compresses last - the listing reads backwards from the run order. The left-to-right form of the same chain is `pipe(resize, watermark, compress)`, where listed order and run order agree. The wiring, the result and the number of stages are identical; only the listing convention differs. Both forms build a new one-argument function, and no stage touches a photo until that function is called with one.

code

pseudocode · 9 lines
pseudocode
function compose(f, g, h)
    return function(x) { return f(g(h(x))) }

function pipe(f, g, h)
    return function(x) { return h(g(f(x))) }

stored = compose(compress, watermark, resize)(photo)
same   = pipe(resize, watermark, compress)(photo)
// both: resize runs first, compress runs last

go deeper

for a junior

Recall that a composition helper's listing order is a convention, not the run order. Be able to point at the stage that touches the input and say why before tracing anything else.

for a middle

Explain the nesting: a right-to-left listing is the nested-call form with the argument removed, so the rightmost stage is the innermost call. Show that the left-to-right form rewires nothing and only reorders the line.

for a senior

Demonstrate how you confirm a helper's direction in code you did not write - reading its implementation, or applying it to two stages with distinguishable effects - rather than trusting the name or the convention you are used to.

for a principal

Frame direction as a convention whose cost is paid on every read and every review, not as a correctness question, and be ready to say what you would standardise on and why.

A photo upload is handled in three steps: **resize** the original, **watermark** the resized image, then **compress** the result for storage. There are two conventional ways to write that wiring as one function, and they disagree about the order the stages appear on the line. ## The two listings - **Right-to-left composition**, usually called `compose`: `compose(compress, watermark, resize)`. The combinator applies the **rightmost** function to the argument, hands that result to the function on its left, and keeps moving left. The listing is the reverse of the run order. - **Left-to-right piping**, usually called `pipe`: `pipe(resize, watermark, compress)`. The combinator applies the **leftmost** function to the argument and walks rightwards. The listing is the run order. Both calls build **one new function of one argument**. Handed the same photo, both produce the same bytes, run the same three stages, and do the same work. The only difference is which end of the written line the data enters. ## Trace it once 1. The built function is called with the original photo. 2. `resize` runs on the original and returns a smaller photo. 3. `watermark` runs on the **smaller** photo - so the mark is sized for the resized image, not the original. 4. `compress` runs on the watermarked photo and returns what is stored. That trace is identical for both listings. If a candidate reads `compose(compress, watermark, resize)` left to right and concludes the original is compressed first, they have read the listing as the run order. ## Reading order against application order | | `compose(compress, watermark, resize)` | `pipe(resize, watermark, compress)` | |---|---|---| | Stage that receives the photo | `resize`, the rightmost | `resize`, the leftmost | | Listing versus run order | reverse of it | the same as it | | Spoken without ambiguity | "compress **after** watermark **after** resize" | "resize, **then** watermark, **then** compress" | | Shape it mirrors | nested calls read outward | steps read in sequence | The words **after** and **then** are the cheapest fix for the ambiguity: "compose these three" says nothing about direction, while "compress after watermark" pins it. ## Why the right-to-left convention exists at all Write the chain out as ordinary nested application and it reads `compress(watermark(resize(photo)))`. The innermost call runs first, so reading the line left to right gives you the **last** stage first. Right-to-left composition preserves exactly that order: it is the nested form with the parentheses and the argument removed, which is why the stage nearest the (absent) argument sits on the right. Left-to-right piping abandons that heritage in favour of the order the data actually moves, which is the order a reader traces when debugging. ## What does not change with direction - The **result**: the same photo goes in and the same bytes come out. - The **join requirement**: each stage's result still has to be acceptable to the stage that receives it next. - The **cost**: the same three stages run once each; direction is a notation, not an optimisation. - The **moment work happens**: composing produces a function; the stages run when that function is applied to a photo. What does change is the reading cost. In a right-to-left listing, the reader's eye moves opposite to the data, and in a long chain that reversal is where mistakes get made - especially in a codebase where both conventions are present and a listing written for one helper is handed to the other. ## What an interviewer is listening for The checkable part of the answer is one sentence: **the rightmost stage in a right-to-left composition is the one that touches the input**. The better answer adds that this is a convention of the helper, not a law, so in unfamiliar code you confirm it - read the helper's few lines, or apply it to a single-stage chain and see which end moves - instead of assuming the convention you happen to be used to. Candidates who say "it depends which helper this is, and here is how I would check" are demonstrating the habit that keeps mixed-direction codebases from producing silently wrong images.

  • Does building the chain do any work to the photo?
    No. Both combinators return a new function of one argument; the three stages run only when that function is applied to a photo. Building it a hundred times resizes nothing. That is why a wiring mistake in the listing shows up as a wrong image later, at the call site, rather than as a failure where the chain was assembled.
  • How would you say the chain aloud so the direction is unambiguous?
    Use the joining word to carry the direction: "compress after watermark after resize" for a right-to-left listing, "resize, then watermark, then compress" for a left-to-right one. "Compose compress, watermark and resize" carries no direction at all, which is exactly how a reviewer ends up approving a reversed chain.
  • You open unfamiliar code and cannot tell which direction its helper uses - what do you do?
    Read the helper; it is usually three lines and the nesting answers it outright. Failing that, apply it to two stages whose effects are distinguishable - one that appends a marker and one that truncates - and see which marker survives. Guessing from the name is unreliable, because names for both conventions vary between codebases.

Reading a right-to-left composition is like reading an address on an envelope: the street comes first but the letter reaches the country first. Same journey, written from the wrong end.

saying these in an interview costs you the question

  • Says compose and pipe produce different final results
  • Claims the first function listed always runs first
  • Cannot say which listed stage receives the original argument
  • Assumes every codebase's compose runs left to right
  • Thinks assembling the chain already processed the photo
open as a page

When you rewrite a right-to-left photo pipeline as a left-to-right pipe, what must match between adjacent stages?

level: middleimportance: should knowfreq 48%

basics

~10 s

Each stage's result must be something the stage that receives it next accepts. That join requirement is identical in both directions; only the direction you read it along the written listing changes.

open as a page

A module exposes both a right-to-left compose and a left-to-right pipe, and one photo chain watermarks before it resizes - how do you find and prevent that mistake?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Find it by asserting the chain's observable output, not its wiring: a listing written for one helper and passed to the other runs backwards while every join still fits. Prevent it by keeping one direction per codebase and naming the helper after its direction.

open as a page

Your codebase uses both composition directions in different modules - how do you settle on one direction and migrate to it?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Neither direction is more correct, so decide on reading cost and existing weight: pick the one most chains and reviewers already use, put the direction in the helper's name, then migrate module by module behind tests of each chain's output rather than in one sweep.

open as a page