skip to content

In a layer that loads linked data on first touch, which line of code emits the extra per-row statements?

level: juniorimportance: must knowfreq 72%

answer

  1. the extra statements have no query call
  2. a field read, not a fetch
  3. look where the graph is walked
  4. count follows rows, not lines
  5. trace the log burst to the touch

basics

~10 s

The line that first touches a deferred link while iterating — an ordinary field read, not a query call. The list read is one statement; every touch inside the iteration adds another.

solid answer

~40 s

In a data-access layer that fills a link on first access, the statement is emitted by whatever reads the link, and that read carries no query text. So the emitting line is wherever the object graph is walked: a loop in a service that reads each row's linked collection, an output serialiser or template that renders every field it can reach, a helper called once per item that quietly does a lookup, or a link left on load-on-touch that a new list path now walks. The list read shows as one statement; the walk shows as a run of near-identical single-row statements differing only in a bound value. Diagnosis means matching that run back to the walk that produced it, not to the query you can see.

go deeper

for a junior

Recall the shape: one list read followed by many near-identical single-row reads. Know that the extras come from touching a linked field while iterating, and that such a touch looks like an ordinary field read.

for a middle

Explain why the emitting line carries no query text, and name the four usual walk sites: a service loop, an output serialiser, a per-item helper, and a link default a new list path now walks.

for a senior

Show the method: attribute each statement to a call site, use the position of the burst in the request to separate a service loop from an output walk, and bisect the output shape to name the field.

for a principal

Frame it as a boundary question. Handing live objects with unfilled links across a layer boundary makes any downstream consumer an emitter, and no review of the service alone can catch it.

A list read that turns into a statement per row leaves a very specific trace: one statement returning the list, then a run of near-identical statements that differ only in a bound value. The log tells you *what* was executed within seconds. The hard part — and the part interviewers actually probe — is *which line of application code produced it*. Nothing can be fixed until that line is named. ## The touch is the call In a layer that defers linked data, an association is filled the first time something reads it. The read that triggers the fill is an ordinary field access or collection iteration. It carries no query text, no function whose name mentions storage, and no visible cost at the call site. The statement is therefore emitted by a line that does not look like data access at all. That inversion is the whole difficulty. Where code issues explicit reads, the emitting line is the one you can search for. Where links fill on touch, the emitting line is wherever the graph is walked — and walking a graph is precisely what mapping, formatting and output code exists to do. ## The four places the walk usually lives - **A loop in a service.** The list is read, then the loop body reads each row's linked object or collection to compute a total, filter, or decision. One statement per iteration, and the loop is at least visible. - **An output serialiser or template.** The loaded objects are handed out whole, and the layer that renders them reaches every field it can. The service shows no loop whatsoever; the burst appears after the handler looks finished. - **A per-item lookup inside a helper.** A helper takes one item and returns something enriched. Called from inside a map or a loop, it reads as one line of business logic and emits one statement per element. - **A mapping default nobody revisited.** A link configured to load on touch when only single rows were read is now walked by a new list path. No line changed; the shape of the caller did. ## Matching the log back to the line | Where the walk happens | What the calling code shows | How the log reads | |---|---|---| | Loop in a service | an explicit loop over the list | list read, then one statement per iteration | | Output serialiser or template | a single return of the loaded objects | list read, then a burst after the handler body ends | | Helper inside a map | one innocuous call per item | list read, then one statement interleaved per item | | Nested links | one call, two levels of collections | list read, then per parent, then per child | The practical technique is to make the log carry position. Statement logging alone gives you the text; what you need is *where it came from*. Options that most layers support in some form: log the call stack (or a truncated one) alongside each statement, tag the unit of work with the request or operation being served, or bisect by trimming the output shape — remove one linked field from what is rendered and see whether the run of extra statements disappears with it. Whichever field makes the burst vanish names the walk. A second tell is *timing within the request*. Extras produced by a service loop are interleaved with the work that follows the list read; extras produced by the output layer arrive in one dense block at the end, after every explicit operation has already completed. That ordering alone often distinguishes the two without reading a line of code. ## Why the count follows rows, not code The number of statements is the number of times the walk visits something, which is the number of rows the list returned — not the number of lines executed. One unchanged line emits one statement against a small data set and hundreds against a large one. This is why the same path behaves differently between two environments, and why a reviewer scanning for "expensive-looking code" finds nothing: the expensive thing is a field read whose cost is paid once per element in a collection whose size is decided at runtime. ## What this diagnosis is not Finding the emitting line is separate from deciding the remedy, and separate from putting a number on the problem. It is also not the same as blaming the store: each individual statement is usually fast and correctly indexed, which is exactly why per-statement timings look healthy while the endpoint does not. The unit of waste is the round trip, repeated, and the fix always begins by naming the walk that repeats it.

  • The burst of extra statements appears after the service method has returned. What does that ordering tell you?
    That the walk is happening downstream of the service — in the layer that renders or maps the loaded objects on the way out. The service handed out live objects with unfilled links, and the renderer touched them. Look at the output shape, not the service body.
  • How do you confirm which linked field is responsible without changing any mapping?
    Bisect the output. Remove or stub one linked field from what is rendered or computed, re-run the same request, and compare the statement log. The field whose removal makes the run of extras disappear is the one being walked. Repeat until each burst is attributed.
  • Every individual statement in the log runs in under a millisecond. Does that rule out a per-row emitter?
    No — it is the usual picture. Each statement is a small indexed single-row read; the cost is the number of round trips, not any one of them. Per-statement timings and slow-statement thresholds will all look healthy while the endpoint is slow.

saying these in an interview costs you the question

  • Blames the store for being slow when every statement is fast
  • Assumes the list read is re-running once per row
  • Looks only in the service and never at the output layer
  • Thinks a method containing no query text cannot emit statements
  • Believes the statement count follows lines of code, not row count