A half-configured chain is stored in a variable and extended two different ways — what decides whether the two interfere?
answer
- same object, or a new one?
- ask what one step returns
- aliasing decides whether branches interfere
- discard the return value and look
- copying buys independent reuse
basics
~20 sWhat each step returns decides it. A step that mutates and returns the same receiver makes both branches one object, so they overwrite each other; a step that returns a fresh receiver leaves the stored one untouched and the branches independent.
solid answer
~40 sThe call sites look identical, so the answer is in the contract, not the syntax. Under the **mutate-and-return-self** contract, every step changes one object and hands that same object back — the variable, both branches and the receiver inside the chain are all the same thing, so the second branch's calls clobber the first's. Under the **copy-and-return-new** contract, each step produces a fresh receiver carrying the previous state plus one addition, so a stored partial chain is effectively a template that any number of branches can extend without interference. The cheap test is to ignore a step's return value: if the configuration still changed, the receiver is being mutated. Copying costs allocations per step; mutation costs you safe reuse and any hope of sharing a partial chain across threads.
code
pseudocode · 11 linesbase = newRoutingTable().route("/orders")
readSide = base.method("GET").handler(listOrders)
writeSide = base.method("POST").handler(createOrder)
// mutate-and-return-self: base, readSide and writeSide
// are ONE object -> method and handler are overwritten,
// and only the POST pair survives
// fresh-receiver-per-step: three distinct values,
// base still describes only the pathgo deeper
Recall that two chains started from one stored variable may or may not be independent, and that the answer comes from what a step returns rather than from how the calls look.
Explain both contracts precisely, and name the probe that separates them: discard a step's return value and see whether the configuration still changed.
Show the failure this causes in production — a configuration silently reduced to its last branch — and say which contract you would pick for templates shared across requests.
Set the rule for a shared library: which contract, whether the closing call snapshots, and how the cost of per-step copying is justified against the reuse it enables.
## Why identical-looking chains behave differently A fluent chain hides which object you are talking to. `x.a().b()` says that `b` is called on whatever `a` returned — it does not say whether that is the same object `a` was called on. As long as the chain is written as one expression, the difference is invisible and harmless. It becomes visible the moment a partly built chain is given a name and used more than once. ## The two contracts | | Mutate and return the receiver | Return a fresh receiver | |---|---|---| | What a step does | changes one object's state | builds a new object from the old state plus one addition | | What the chain is | one object, called repeatedly | a series of values, each derived from the last | | A stored partial chain | an alias: every branch shares it | a template: branches are independent | | Ignoring a step's return value | harmless, the change already landed | the step is lost, nothing changed | | Cost | none beyond the state itself | an allocation and a copy per step | | Sharing across threads | unsafe without extra coordination | safe if the receivers are genuinely not modified | Both contracts are legitimate. The mistake is not picking one; it is failing to say which one you picked, because the call site cannot tell. ## The branching case, step by step Take a routing table where `route(path)` opens an entry and later steps refine it, and consider a partial chain saved as `base`: 1. `base` is built by calling `route("/orders")` on a fresh table. 2. One branch adds a read handler to `base`. 3. Another branch adds a write handler to `base`. Under mutation there is only ever **one** entry: step 3 overwrites what step 2 recorded, and both names refer to the same table, so the "two" configurations are one and the last writer wins. Under copying there are **three** distinct values — the template plus two derivations — and the template still describes only what it described when it was stored. The defect this produces in real code is not a crash. It is a configuration that is quietly half of what the author wrote, discovered much later, at a call site that looks correct in isolation. ## Telling which contract you have Without documentation, these probes distinguish them: - **Discard a return value.** Call a step and throw its result away, then close the original receiver. If the step took effect anyway, the receiver is mutated. - **Close the same partial chain twice.** If the second result reflects calls made after the first result was produced, the state is shared — and the closing call did not take a snapshot. - **Compare identity.** If a step's result is the same object it was called on, the contract is mutation by definition. - **Look at the return type.** An API that deliberately returns a narrower type per step is usually building new values, since a mutated object cannot change its own type. ## Does the closing call snapshot? A third question hides behind the first two: whether the result produced by the closing call keeps looking at the receiver's state or copies it out. If it copies, mutating the receiver afterwards leaves already-produced results alone — a useful property, and one that lets a mutable receiver be reused deliberately as a template. If it does not copy, a result produced early can change under its holder, which is the more surprising of the two and worth asking about in review. ## Practical guidance - Prefer **fresh receivers** when partial chains are meant to be stored, passed around, or shared — configuration templates, per-request derivations of a common base. - Prefer **mutation** when a chain is always written and closed in one expression and the state is large enough that copying per step would be wasteful. - Whichever you pick, say it in the API's own words, and make the closing call's snapshot behaviour explicit at the same time. - Treat "can I reuse this half-built thing?" as a design decision, not an accident of how the first step happened to be implemented. It is the question that decides whether a partial chain is a value or a handle.
- What does the closing call's snapshot behaviour add to this picture?It decides whether an already-produced result can change later. If the closing call copies the accumulated state out, mutating the receiver afterwards cannot touch results already handed over. If it keeps a view of the receiver, a result can drift under its holder — the more surprising contract, and one worth stating explicitly.
- Can a partial chain be shared across concurrent work?Only under the fresh-receiver contract, and only if the shared receiver is genuinely never modified after publication. A mutating receiver shared by two workers is an ordinary data race: interleaved steps produce a state neither caller wrote.
saying these in an interview costs you the question
- Assumes every fluent step returns a brand-new object
- Thinks storing a partial chain automatically snapshots it
- Says ignoring a step's return value is always harmless
- Believes chaining implies the receiver is safe to share
- Dismisses per-step copying as never worth measuring