skip to content

Why does a surface that hands back one whole record per iteration cost more than reading only the two fields you need?

level: middleimportance: should knowfreq 52%

answer

  1. records are assembled, not stored
  2. one read per column, per row
  3. cost tracks the table's width
  4. each field wrapped on the way out
  5. some containers flatten to one representation

basics

~20 s

A record does not exist in memory: columns are stored separately, so each iteration gathers one value from every column, wraps each, and builds a container. You pay for the table's full width even when the body reads two fields.

solid answer

~50 s

The data is held column by column, each column a packed run of values of one representation. There is no row sitting anywhere to hand back, so the per-record iteration surface has to assemble one on every pass: read the value at that position out of every column, wrap each as a language object, build a container, hand it over, discard it. The body then reads two fields and the rest were gathered for nothing. That is why the per-iteration cost tracks how wide the table is rather than how many fields you asked for, and why narrowing to the columns you actually read is usually the cheapest single improvement. Some per-record containers carry one representation for all fields, which adds a conversion per field and can hand back a value of a different kind than was stored.

go deeper

for a junior

Know that the data is held column by column, so a record has to be put together before anyone can look at it. That assembly is real work and it happens on every single iteration.

for a middle

Explain that the assembly reads one value from every column, not only the ones the body names, so the per-iteration cost tracks the table's width; narrowing to the used columns first is the cheap fix.

for a senior

Watch for the flattening case: where the per-record container carries one representation for all fields, whole-number identifiers can come back fractional and comparisons downstream quietly change meaning.

for a principal

Weigh whether per-record access belongs in shared transform code at all, given that its cost grows with a table other teams keep widening and nothing in ordinary review flags that growth.

## A record is not stored anywhere The picture most people carry is a table as a stack of rows, so handing one back looks like reading a line off a page. That is not how these objects are laid out. A table here is a set of columns side by side, each one **a packed typed buffer** - values of a single representation laid end to end and addressed only by position. The row at position 500 is not stored as a unit at all. It is the 500th entry of the first column, the 500th entry of the second, and so on, in as many separate places as there are columns. So **the per-record iteration surface** - the surface that hands records back one at a time so you can write an ordinary loop over them - cannot point at a record. It has to construct one, on every iteration, before your body can name a single field. ## What one iteration actually costs For a table of *k* columns, one iteration does roughly this: - read one value out of each of the *k* packed buffers; - produce a language object for each of them, because your body will hold them as ordinary values rather than as bytes (**a boxed value**: a full object with its own header and a pointer to it); - build a container holding all *k* of them and hand it over; - discard the whole thing when the iteration ends. The body then reads two fields out of that container. The other *k* minus 2 were gathered, wrapped and thrown away. This is the part candidates miss: **the per-iteration cost tracks the width of the table, not the number of fields the body asked for.** Going from forty columns to two removes roughly ninety-five per cent of the gathering and wrapping from every iteration, and it is normally the cheapest change on the table. Note what does *not* change per iteration: the number of rows. More rows means the same cost paid more times, which is a different thing from a more expensive iteration, and keeping the two apart is most of what makes this question answerable. ## The flattening trap There is a second cost that is easy to miss because it changes the values, not just the timing. Some per-record surfaces build a container that carries **one** representation for all fields. A record spanning a whole-number identifier, a fractional measurement and a flag cannot keep three representations inside such a container, so the fields are brought to a single common one on the way out. Two things follow: 1. There is conversion work per field per row, stacked on top of the gathering and the wrapping. 2. The value you read back may not be the value that was stored. A whole-number identifier can arrive as a fractional value and then print, compare or serialise differently three steps downstream - a defect that is invisible at the loop and visible much later. Other surfaces build a heterogeneous record instead, where each field keeps the representation of the column it came from. Those cost less and distort nothing. Both kinds exist across this family, and they frequently sit side by side on the same object, so the useful habit is to establish which one you are holding before reasoning about either its cost or its values. ## The variables, separated | what changes | effect on one iteration | |---|---| | more columns in the table | proportionally more values gathered and wrapped | | more fields read in the body | almost nothing; they were all gathered anyway | | a flattened per-record container | a conversion per field, and representations that may shift | | a heterogeneous per-record container | no conversion; each field keeps its own representation | | more rows | no change per iteration, simply more iterations | ## What to do instead - **Narrow first.** If you must loop, reduce the table to the columns the body actually reads. The gathering cost falls with the width immediately and the change is one line. - **Walk columns, not records.** If the body reads two fields, take those two columns and step through their values in parallel. Nothing needs to be assembled at all. - **Prefer not to loop.** Expressing the body once for the whole column - working column-wise - removes the assembly rather than shrinking it, because columns are already the form the data is in. The shape of answer an interviewer is listening for is short: a record is assembled rather than stored, the assembly reads every column, so the cost is set by how wide the table is - and the real fix is to stop asking for records.

  • Does narrowing the table to the two columns before the loop actually help?
    Yes, and measurably. The assembly reads one value out of every remaining column on every iteration, so going from forty columns to two cuts roughly twenty times the gathering and wrapping out of each pass. It shrinks a constant rather than changing the shape of the curve: the per-row cost is still paid once per row, it is simply much smaller.
  • Two per-record surfaces exist on the same object, one flattening the record and one keeping each field's representation. Which do you reach for?
    The one that preserves each field's representation. The flattening form has to bring every field to a single common representation, which costs a conversion per field and can silently change a value's kind, so a whole-number identifier comes back fractional. Neither form removes the per-row assembly, so preferring one is damage control rather than a fix.

saying these in an interview costs you the question

  • Assumes a record sits contiguously in memory ready to hand back.
  • Thinks the cost depends on how many fields the body reads.
  • Ignores that a flattened record can change a field's representation.
  • Believes narrowing to the needed columns first makes no difference.
  • Calls the per-record surface free because it does not copy the table.