How do you check whether a deferred link is already loaded before touching it, and when is that check the right tool?
answer
- ask the layer, not the object
- asking must not be a touch
- per attribute, not just per object
- a guard for generic code only
basics
~20 sAsk the layer, not the object: a load-state predicate reports whether a link or one attribute has been fetched, without touching it. Use it in generic code that walks object graphs and cannot know the caller's fetch plan.
solid answer
~50 sThe predicate lives on the layer's utility surface rather than on the mapped class, because anything called on a stand-in may be intercepted — an "am I loaded" method could answer by loading. Per-attribute variants matter, since an object can be present while one of its collections is not. The check belongs in generic traversal: formatters, serialisers, transfer-model mappers, audit and diff walkers, all invoked under fetch plans they did not choose. Given the answer they should skip the branch or mark it, never fetch quietly. Scattered through business logic it is a smell instead: it makes behaviour depend on what ran earlier, and the real decision — what this use case's query should bring back — has been pushed downstream. When you do want the data, force the load explicitly rather than letting a stray read do it.
go deeper
Know that the layer can tell you whether a link has been fetched, and that a null check cannot: an unloaded link is still a non-null object sitting in the field.
Explain why the predicate is on the layer rather than the object — a call on a stand-in could be intercepted — and why per-attribute answers matter for collections.
Draw the line in real code: guards in generic traversal that cannot know the fetch plan, and query changes everywhere else. Show that you would force a load explicitly rather than rely on a stray field read.
Treat it as a boundary policy. Decide where mapped objects stop being allowed to query, what crosses that boundary instead, and how the rule is enforced rather than remembered.
## The predicate, and why it lives outside the object Every layer that defers has to record whether a link has been fetched, and the useful ones expose that record. The exposed form is a **predicate on the layer's own utility surface**, taking the object (and often the name of one attribute) and answering yes or no. It does not live as a method on the mapped class, for two reasons: mapped classes should not carry persistence machinery, and — more importantly — calling anything on a stand-in risks being intercepted, so a "am I loaded" method could answer by loading. The predicate inspects the placeholder's flag from outside the interception, so it costs nothing. Per-attribute variants matter: an object can be fully present while one of its collections is not, so "is this object loaded" and "is this attribute of it loaded" are different questions. ## When the check is the right tool The check earns its place in **generic traversal code**: anything invoked from many call sites, over objects whose fetch plan it did not choose. - Output formatters and serialisers walking an object graph. - Object-to-object mappers building a transfer model. - Audit, diff and change-description code comparing two versions of a graph. - Debug rendering and structured logging of a mapped object. All of these share one property: they cannot know what the caller fetched, and they touch everything they can reach. Given the predicate, they can skip an unloaded branch, emit a marker for it, or report the omission — and the statements that would otherwise fire never do. The important part is what such code should **not** do with the answer: it should not quietly load. The caller asked for output, not for a fan of extra reads, and it has no way to see the cost. ## When it is the wrong tool Scattered through application code, a load-state check is a smell rather than a fix: | Symptom | What it really means | |---|---| | Branching on load state inside a service method | The query for this use case did not fetch what the use case needs | | A check before every association read | The decision has been pushed from the query to the caller, per read | | Behaviour that differs by what ran earlier | Load state is history, so the code is now order-dependent | | Tests that pass only in a particular sequence | The same order dependence, surfacing | The honest fix is almost always upstream: decide, in the query that serves the use case, which links come back. The load-state predicate does not choose a fetch plan; it reports the consequences of one. ## The companion operation Alongside the predicate, layers offer a way to **force the load deliberately** — initialise this link now, while I still can and while I am the one deciding. It is the right tool in narrow places: 1. A boundary that is about to hand objects to code which must not query — after that point, the only options are a present value or a failure. 2. A batch job that has genuinely decided to pay per object and wants the payment to be explicit and greppable rather than hidden in a field read. 3. Test setup that wants a graph in a known state. An explicit initialise is strictly better than a stray field read used for the same purpose: it says what it is doing, it survives refactoring that removes the read, and it shows up in review. ## Reading the answers honestly Three cautions: - **A non-null reference is not a loaded one.** The stand-in and the instrumented collection both exist from the moment the parent is materialised. - **The answer is a moment in time.** Anything that ran earlier in the same unit of work may have loaded the link, so a check that passes locally can fail under a different call order. - **Layers differ in what they expose.** Some answer per object only, some per attribute, some also say whether a value is present without having been read. A layer with no tracked set and no deferral has nothing to ask — its results are whatever the query selected, and an absent branch is absent for good.
- Why does the load-state check live on the layer rather than as a method on the mapped object?Anything you call on a stand-in can be intercepted and fault the row in, which would defeat the purpose. The layer inspects the placeholder's flag from outside the interception, so the answer costs no statement. It also keeps persistence machinery off the mapped class, which is a design win independent of the cost.
- What should generic code do once it detects an unloaded link?Skip it and say so — omit the branch, emit a marker, or fail loudly in tests. What it must not do is fetch quietly: the call site that triggered the traversal never asked for that data and cannot see the statements. If a consumer genuinely needs the branch, the fetch belongs in the query that served that consumer.
- Why can a load-state check pass locally and fail in production?Because it reports history, not policy. Whether a link is loaded depends on everything that ran earlier in the same unit of work, so a different call order, a warmed shared object or an extra step upstream changes the answer. Code that branches on it is order-dependent by construction, which is exactly why it belongs only in traversal guards.
saying these in an interview costs you the question
- Calls a method on the object itself to find out whether it is loaded
- Sprinkles load-state checks through business logic instead of fixing the query
- Assumes a non-null reference means the link has been loaded
- Treats the check as a performance fix rather than a guard for generic traversal
- Assumes the answer is stable, when anything that ran earlier may have loaded it