Why does a mapper replace the collection instance a constructor assigned with its own instrumented collection?
answer
- your instance does not survive
- a bag of elements has no history
- declared type must allow the swap
- mutate it, never replace it
basics
~20 sThe layer needs a collection it controls: one recording whether the rows were read, which object owns it, and what was added or removed. On load it installs its own implementation, discarding the constructor's instance.
solid answer
~40 sA plain collection is a bag of elements with no history, and deferral plus change detection both need history. So the layer installs its own implementation carrying a loaded flag, the owning object and field, and a record of additions and removals. Four consequences follow. The field must be declared as a general collection interface, or the layer's instance will not fit and the mapping silently loses deferral. A reference to the constructor's collection kept elsewhere is now dead — writes to it are never persisted. Assigning a brand-new collection over the field throws away the tracking, and reconciliation can mean deleting every child row and re-inserting it. And a non-null collection field proves nothing about whether the rows were read.
go deeper
Know that the collection you initialised is not the one you get back after a load. Add and remove through the collection the layer gave you, and never assign a new one over the field.
Explain what the layer's instance carries — loaded flag, owner, add and remove record — and why the field's declared type has to be a general interface for the substitution to happen at all.
Diagnose the two failure modes from evidence: deferral that never happens because the swap could not occur, and a one-element change that emits a delete-and-reinsert storm because the field was reassigned.
Decide how mapped collections are exposed at all. Read-only views plus mutation methods on the owning object make the do-not-replace rule enforceable instead of a convention people forget.
## The swap Map a collection on an object and you will usually initialise the field where it is declared or in the constructor, so that the object is usable before it is ever stored. When the layer loads that object, it does not keep your instance. It assigns **its own collection implementation** into the field, and from that point every read and write the caller performs goes through the layer's code. The instance you created is discarded. Any reference to it that another part of your code kept is now a reference to a detached, ordinary collection that nothing will ever persist. ## What the layer's instance carries that yours cannot - **A loaded flag** — whether the element rows have been read at all. Deferral of a collection is impossible without somewhere to record that. - **The owning object and the mapped field** — so the layer knows which rows to fetch and which foreign key to write when an element is added. - **A change record** — which elements were added and removed since load, which is what drives inserts, deletes and cascade decisions at flush time. - **A snapshot for comparison**, in layers that detect changes by diffing rather than by recording operations. - **Ordering and index bookkeeping** where the mapping declares an order. A plain collection instance carries none of that. Handing one to the layer is handing it a bag of elements with no history. ## The consequences the interview is looking for | What you do | What happens | |---|---| | Keep a reference to the constructor's collection and write to it | Writes go to a discarded object and are never persisted | | Declare the field as a concrete implementation type | The layer cannot assign its own instance; deferral and tracking are lost, or startup fails | | Assign a freshly built collection over the field | Tracking and the loaded flag are gone; the layer must reconcile, often by removing every child row and re-adding | | Clear the layer's instance and re-add | Tracking is intact; the layer sees a precise add/remove set | | Treat a non-null field as proof of loaded data | The instance always exists; the rows may not have been read | **Declared type matters.** Because the layer must be able to assign its own class into the field, the field has to be declared as a general collection interface rather than a specific implementation. A field typed as a concrete class is a field the layer's instance does not fit into. This is the single most common cause of "deferral is configured but does not happen" in a collection mapping. **Replacement is not free.** Assigning a new collection reads, to the layer, as "the old contents are gone and these new contents have arrived". Depending on how the association is mapped, that can be carried out as a delete of every existing child row followed by an insert of every new one — a statement count proportional to the whole collection for a change that touched one element, plus whatever the cascade rules then do to the removed rows. Mutating the instance you were given avoids all of it. ## Practical rules 1. **Initialise the field once, then never assign to it again.** Add, remove and clear; do not replace. A setter that overwrites the collection is worth deleting outright. 2. **Declare the field as the general interface**, and let the layer choose the implementation. 3. **Never cache the collection reference elsewhere** — not in another field, not in a local held across a load boundary, not in a snapshot for later comparison. 4. **Expose the collection to callers as a read-only view** and mutate it through methods on the owning object. This keeps the "do not replace" rule enforceable rather than aspirational. 5. **Do not read emptiness as a cheap guard.** The swapped-in instance answers questions about rows, and answering may require reading them. ## Where layers differ Not every data-access layer does this. A layer that maps rows to plain result objects with no tracked set has no reason to substitute anything: the collection you get is whatever the mapping built, it is fully populated because it came from the query, and writing to it changes nothing anywhere. The swap belongs to layers that track loaded objects and derive statements from changes; recognising which kind of layer you are in tells you whether the collection in your hand is a live handle or a plain value.
- What goes wrong if the field is declared as a concrete collection implementation instead of a general interface?The layer cannot assign its own class into a field typed as a specific implementation. Depending on the layer you get a startup failure or, worse, a silent fallback in which the swap never happens — so that field has no deferral, no loaded flag and no change record, and its contents are worked out by brute comparison or not at all.
- Why is assigning a new collection to the field risky when mutating the existing one is fine?The instance you assign carries no tracking and no loaded flag, so the layer must reconcile old contents against new. That is commonly carried out as a delete of every existing child row followed by an insert of every new one, with the cascade and ordering consequences that implies. Clearing and re-adding on the layer's own instance keeps the precise add and remove set.
saying these in an interview costs you the question
- Assumes the collection assigned in the constructor is the one the layer hands back
- Caches the collection reference in another field and keeps writing to it
- Declares the field as a concrete implementation and still expects deferral
- Replaces the whole collection on update and assumes it costs one statement
- Believes the collection is loaded simply because the field is non-null