Why does deferring a single wide column need different machinery than deferring a link to another object?
answer
- a reference can be occupied
- a field cannot
- the class itself must be rewritten
- silent no-op without instrumentation
basics
~20 sA link is reached only through a reference, so the layer can put a stand-in there. A deferred column is a field of the object you already hold, so the class's own field reads must be instrumented instead.
solid answer
~50 sThe stand-in trick works because every route to the target passes through a reference the layer controls. A wide value — a document, an image, a long text — sits inside the object already in hand, so there is nothing to occupy; deferring it means intercepting the read of that one field on the real instance, which requires rewriting the class's accessors or field reads when it is built or loaded. That extra step is why the usual failure is silent: the mapping says deferred, the instrumentation never ran, and the column is selected with the rest of the row while nothing errors. Two more properties matter. The first read commonly fetches the whole deferred group of that row, not one column. And the load is per object, so a listing that shows the value for every row pays a statement per row.
go deeper
Know the distinction: a link can be replaced by a placeholder object, a single column cannot, because it is a plain field of an object you are already holding.
Explain what deferring a column really requires — interception of the field read on the real instance, arranged by rewriting the class — and why that is a separate step from the mapping.
Recognise the silent no-op from evidence: read the emitted column list, not the configuration. Know that the fetch is per object and per group, so a wide-column deferral can help a detail screen and do nothing for a list.
Weigh it against the alternatives. A separate mapped row gives the value its own lifecycle without instrumentation; a narrow read path for list screens avoids the mapped object entirely and is usually the cheaper thing to reason about.
## Why a link can be deferred cheaply Deferring a **reference** works because the caller's only path to the target is that reference, and the layer controls what sits in it. Put a stand-in there and every route to the target passes through code the layer wrote; the load can be arranged on first use with nothing but ordinary object mechanics — a generated subtype, an intercepted call, a fetch, a forward. Deferring a **single wide column** — a document, an image, a long text body, a serialised blob — has no such foothold. The value is a field of the object the caller already holds. There is no separate reference to occupy, and no method call that has to happen before the value can be read. Handing back a stand-in for the whole object would not help either: the caller wanted this object, and the moment they read the cheap fields the stand-in loads the row, wide column included. ## What deferring a column actually requires The read of that one field has to be intercepted **on the real instance**. That means the class's own code must be changed so that reading the field consults the layer first: - rewriting accessors, or the field reads themselves, when the class is compiled or when it is loaded into the runtime; - recording per-object which deferred fields are present; - issuing a statement that selects the missing column for that row on first read. That rewriting step is machinery the reference case never needed, and it is separate from the mapping. This is why the most common failure here is a **silent no-op**: the mapping declares the column deferred, the instrumentation step never ran, nothing intercepts the read, and the column is selected with the rest of the row exactly as before. Nothing errors; the query is just as wide as it was. Confirm by reading the emitted statement's column list, never by reading the configuration. ## The behaviour once it works | Property | Deferred link | Deferred column | |---|---|---| | Mechanism | Stand-in in the reference | Instrumented field read on the real object | | Extra build step | None | Yes — class rewriting | | Granularity | Per reference | Per field, usually per declared group | | First touch fetches | The target row | The whole deferred group of that row | | Statements when reading N objects | One per untouched link touched | One per object whose deferred field is read | | Silent-failure mode | Rare | Common: no instrumentation, no deferral | Two rows of that table deserve emphasis. **Grouping**: layers commonly let several deferred fields share a group, and the first read of any member fetches the group in one statement — so the cost is per group, not per field, and a badly drawn group can fetch a wide column you did not want. **Per object**: the load fires once for each object whose deferred field is read, so a listing that shows the wide value for every row pays a statement per row and is worse off than if the column had simply been selected once with the list. ## The two alternatives worth naming 1. **Model the wide value as its own object behind a reference.** A one-to-one link to a separate mapped row turns the problem back into the reference case, which needs no instrumentation, and gives the wide value its own lifecycle: fetched, replaced or skipped independently. The price is an extra table and a join whenever both halves are wanted together. 2. **Use a read path that selects only the columns the screen needs.** For list and search screens, which are where wide columns hurt, not reading the mapped object at all is simpler than making the mapped object read less. Deferring the column is the right answer mainly when the same mapped object is used on both the narrow and the wide path, the value is genuinely large, and the instrumentation step is already part of the build. Otherwise one of the two alternatives costs less to reason about. ## How to tell which situation you are in Three questions settle it quickly, and none of them require reading the layer's documentation: 1. **Does the emitted statement still name the column?** If it does, the deferral is not happening, whatever the mapping says, and no amount of tuning elsewhere will change the width of that read. 2. **How many objects does the hot path read at once?** One object makes a per-object fetch negligible; a page of them turns the same fetch into a statement per row. 3. **Does anything ever want the narrow object and the wide value together?** If yes, a separate mapped row costs a join every time; if no, it is free and simpler. The failure worth remembering is the quiet one. A deferred reference that is not deferred announces itself as a slow page eventually; a deferred column that is not deferred announces nothing at all, because the read still returns the right value — just with far more bytes crossing the wire than anyone intended. Only the statement's column list tells you the truth.
- Why can a deferred-column setting appear to be ignored entirely?Because the instrumentation step never ran. The mapping declares intent, but with nothing rewriting field access there is nothing to intercept, so the column is selected with the rest of the row and the setting has no visible effect at all. Confirm by inspecting the emitted statement's column list rather than by trusting the configuration.
- When is modelling the wide value as its own object better than deferring the column?When it has an independent lifecycle: written rarely, read rarely, replaced as a unit. A separate mapped row behind a reference turns the problem back into the plain stand-in case, which needs no instrumentation, and lets the value be fetched or skipped on its own terms. The cost is an extra table and a join when both halves are wanted together.
saying these in an interview costs you the question
- Thinks a stand-in object can defer a single column of the row it belongs to
- Assumes declaring a column deferred is enough, without the instrumentation step
- Believes the first read fetches only that column and never its whole group
- Expects deferring a wide column to help a listing that renders the value anyway
- Confuses a deferred column with a nullable one that simply holds no value