When a mapping stage turns order lines into shipment lines, what does it leave untouched and what does the result still share?
answer
- the promise is about the container
- new collection, same elements inside
- selection constructs nothing at all
- a transform can reuse the input's fields
- the function is outside the stage's contract
basics
~20 sUntouched means the source collection itself: it is not resized, reordered or written, and the result is a separate collection. Not untouched are the elements — a filtered result holds the very same element objects the source holds.
solid answer
~50 sThe guarantee is about the container, not its contents. A mapping stage reads the source collection and returns a new one; it never inserts, removes, reorders or overwrites a slot in the source, so a later append to the result cannot be seen through the source. What crosses the boundary is references. A filtering stage's result holds the identical element objects the source holds, and a transform that builds a shipment line by reusing the order line's address hands the same address object to both collections. A write through either side is then visible from the other. Two things therefore still break the illusion of independence: sharing at the element level, and a transform or predicate that writes to the element it was handed — the stage kept its promise about the collection while the function quietly mutated its contents.
code
pseudocode · 9 lineslines = [ lineA, lineB ] // lineB.quantity is 2
open = filter(lines, isNotCancelled) // neither line is cancelled
// open is a NEW list, but open[1] is lineB itself, not a copy
open[1].quantity = 99
print lines[1].quantity // prints 99
append lineC to open
print size(lines) // prints 2 - the source is untouchedgo deeper
Remember that these stages read and build: the collection you started with still has the same elements in the same order afterwards, and what comes back is a different collection you are free to add to.
Separate the container from its contents. Explain that a filtering stage constructs nothing, so its result holds the same element objects, and that a transform may hand shared fields to both sides.
In review, treat the stage contract as free and the functions as the thing to read. A transform or predicate that writes to the element it was given mutates shared state once per call, in traversal order, with nothing in the pipeline to stop it.
The independence teams assume from a pipeline is bought at the element level, not the stage level. Deciding whether your domain elements can change after construction is the call that makes every pipeline in the codebase safe or merely lucky.
## What "untouched" actually covers Both stages are defined as **read-and-build**: walk the source, decide per element, put results into a collection the stage created. The guarantee that follows is precise and narrow — the **source collection object** is not written to. Nothing is inserted into it, nothing is removed from it, no slot is overwritten, and its ordering is not disturbed. That is why you can run three different pipelines over the same order-line collection and have each one see the same input. It is also why the result is genuinely yours: appending to it, sorting it or handing it to another stage cannot be observed through the source. The two collections are separate containers from the moment the stage returns. ## What crosses the boundary anyway What the guarantee does **not** say is that the two collections stop sharing anything. There are three distinct levels, and only the first is protected: | level | filtering stage | mapping stage | |---|---|---| | the collection itself | new container, source untouched | new container, source untouched | | the elements in it | the **same** element objects as the source | whatever the transform returned | | what those elements point at | shared with the source by definition | shared wherever the transform reused a field | A filtering stage is the clearer case: it constructs nothing. Every element it keeps is the element the source holds, so if elements are mutable, a write through the filtered result is a write the source can see. A mapping stage looks safer because the transform usually builds something new — but only the outermost object is new. A transform that builds a shipment line and reuses the order line's address object has produced two elements that point at one address. Change the address through the shipment line and the order line's address changed too. ## How the promise gets over-read - **"The stage is non-destructive, so I can mutate freely."** The stage is non-destructive about the collection. Mutating an element reached through the result reaches the source's element. - **"Filtering copies what it keeps."** It does not. Selection and copying are different operations, and nothing in the contract asks for a copy. - **"Nothing was rejected, so I got the source back."** An eager stage builds a collection regardless. Relying on getting the same object back is relying on an optimisation nobody promised, and code written that way breaks the first time the predicate rejects one element. - **"The source is unchanged after the pipeline ran."** True of the container only, and only if the transform and the predicate wrote nothing. A transform that stamps a field on the element it was handed has changed the source's contents while the stage honoured its own contract. ## The function is the hole in the guarantee This is the part worth saying out loud in an interview. A mapping stage cannot enforce anything about the transform it is given, and a filtering stage cannot enforce anything about the predicate. The stage's promise covers what the *stage* does. If the function writes to its argument, or appends to some collection elsewhere, or increments a counter, those effects happen exactly as many times as the function is called and in whatever order the stage walks the source. So "this pipeline does not mutate anything" is a claim about two things together: the stage contract, which you get for free, and the functions, which you have to read. Reviewers who check only the first get surprised by the second. ## Stating the guarantee so it cannot be over-read A formulation that survives review: *the stage reads the source and returns a new collection; it never inserts into, removes from, reorders or overwrites the source. It says nothing about what the two collections' elements share, and nothing about what the transform or predicate does inside.* When you need real independence — a result you can hand to another part of the system without it being able to reach back — the stage does not give it to you. You get it by making the elements themselves unable to change, or by having the transform build elements that share nothing with their input. Both are decisions about the elements, taken by you, not properties of mapping or filtering.
- The transform builds each shipment line from an order line and reuses the order line's address object. What has the result inherited?A reference, not a copy. The shipment line's address is the order line's address, so a write through either is visible through the other. "The mapping stage did not touch the source" is a statement about the collection, not a promise that the two collections share nothing.
- Can a pipeline change the source's contents even though neither stage writes to the source collection?Yes, through the functions. A transform that stamps a status onto the element it was handed, or a predicate that records something on the element it is judging, mutates objects the source holds. The stage kept its own contract; the function did the damage, once per call.
- What would it take for the result to be genuinely independent of the source?Either elements that cannot change after construction, or a transform that builds each output without retaining any reference from its input. Both are choices about the elements. Neither stage can supply independence on its own, because neither stage is allowed to construct or copy what it is selecting.
saying these in an interview costs you the question
- Says a filtering stage copies each surviving element into a new object.
- Claims a mapping stage removes the transformed elements from the source.
- Reads "the source is unchanged" as meaning nothing is shared afterwards.
- Relies on getting the source collection back when nothing is rejected.
- Thinks writing inside the transform is safe because the stage is non-destructive.