skip to content

In your archiver, the hand-written body for one record type drifted from the general body - why does the bug appear only at some call sites?

level: seniorimportance: must knowfreq 48%

answer

  1. same name, two behaviours
  2. nothing compares the two bodies
  3. selection follows static knowledge
  4. a generic caller may keep the general body
  5. share the invariants, specialize the representation

basics

~20 s

Because which body runs is decided by what each call site statically knew about the type, not by the value. A site that fixed the argument takes the drifted body; a site still holding it generically can keep running the general one.

solid answer

~50 s

Nothing checks a specialized body against the general body's behaviour - they share a name and a call shape, not a contract. So when the narrower body quietly stops writing the trailing checksum, you now have one operation with two meanings, and selection decides which you get. Selection follows static knowledge: a call that names the concrete record type binds the narrower body, while the same operation reached from inside another generic routine that has not fixed its placeholder may have been bound to the general body. Same input, same value, two different archives. That is why it reproduces on one path and not another, and why logging the value tells you nothing. The repair is structural: keep the invariant-bearing steps in a shared step neither body can replace, let an override change only representation and cost, and run one conformance suite over every body.

code

pseudocode · 16 lines
pseudocode
// the override drifted: it no longer writes the trailing checksum
specialize writeRecord for <BlockRecord>(out, record)
    out.writeRaw(record.bytes)

function archiveAll<T>(out, records)   // T is still a placeholder here
    for each r in records
        writeRecord(out, r)            // which body was bound at this point?

writeRecord(out, oneBlockRecord)       // argument fixed: narrower body,
                                       // archive has no checksum

archiveAll(out, listOfBlockRecords)    // if this body was bound once against
                                       // an unfixed T, every element takes
                                       // the general body - checksum present

// same records, two archives, and the value is identical on both paths

go deeper

for a junior

Recall the danger in one sentence: a hand-written body for a particular type can behave differently from the general one, and nothing warns you. Knowing that the two are not checked against each other is the takeaway here.

for a middle

Explain why the same value can produce two outputs: selection is decided by what the call site knew about the type, so a direct call and a call from inside another generic routine can bind different bodies.

for a senior

This is the production version. Show the diagnosis - stop inspecting the value, read the selection point - and the repair: invariant steps in a shared step, one conformance suite instantiated per specialized argument, overrides limited to cost and layout.

for a principal

The question you own is whether any override may change observable behaviour at all. Making that a published rule, enforced by a suite rather than review, is what keeps a shared operation from meaning two things.

## Two bodies, one name, no checker When you supply a hand-written body for a chosen type argument, exactly one thing is verified: it can be called where the general body could. **No tool compares what the two bodies do.** There is no inherited contract, no signature for behaviour, no warning when one of them stops writing a trailing checksum, reorders fields, or treats an empty record as a no-op. That freedom is why the mechanism exists - a fixed-width record genuinely can be written in one pass rather than field by field - and it is also the whole hazard. The moment an override's observable behaviour differs from the general body's, the program has **one operation with two meanings**, and which meaning you get is a property of the call site. ## Why it reproduces on one path and not another Selection happens where the type argument is fixed, and different call sites fix it in different places: - A call that names the concrete record type fixes the argument there; the narrower body is a candidate and wins. - A call inside another generic routine that has **not** fixed its own placeholder selects against what that context knows. If the routine's body was bound once against an unknown argument, every element it processes runs the general body - even elements whose concrete type has an override. - Languages differ here: some fix the argument again for each concrete argument the routine is used with, some bind the body once. Both designs are common, so "does the override reach inside a generic caller?" is a property of the platform, not a universal. The operational consequence is the one that costs a day: **the value is identical on both paths**, so logging the record, dumping it, or re-running it in isolation all show the same input producing different output. The variable is the static context, and it is invisible in the data. ## The second route to the same divergence Even with one call shape, an override can be missed. Where a language requires the narrower body to be **visible** at the point the argument is fixed, a translation unit that never sees the override quietly uses the general body, and one that does uses the override. The archive then depends on the include structure of the build. Some languages make that an error; some do not diagnose it at all. ## Keeping the bodies honest 1. **Split invariant from representation.** Put the steps that define the output format - framing, the trailing checksum, the length prefix - in a shared step that no override can replace, and let the override supply only the part that varies: how the payload bytes are produced. 2. **State the contract as text next to the general body.** "Every body under this name emits a length prefix, a payload and a checksum, in that order." An override author who breaks it is then wrong on purpose rather than by accident. 3. **One conformance suite, run against every body.** Write the tests against observable output - round-trip, byte layout, empty and maximal records - and instantiate them for each specialized argument as well as a general one. This is the only mechanism that actually catches drift. 4. **Diff the two outputs in the suite.** For any argument with an override, assert that the override's output and a general-body reference implementation agree on everything the contract covers. 5. **Specialize for cost, never for meaning.** If the narrower body needs to produce a *different* archive, it is a different operation and deserves a different name. ## What belongs in which body | Concern | General body | Specialized body | |---|---|---| | Output format and ordering | defines it | must reproduce it exactly | | Checksum, framing, length prefix | writes it | should inherit a shared step, not re-implement it | | How the payload is laid out | field by field | free to differ - this is the reason it exists | | Error and empty-input handling | defines it | must match, including what it throws or returns | | Performance | the baseline | free to differ - the other reason it exists | ## The diagnosis in one line When output differs between two paths carrying the same value, stop looking at the value. Ask which body each path selected, and that question is answered by reading how much each call site statically knew about the type - not by any run-time evidence the program can produce for you.

  • Why does logging the value not help you find this?
    Because the value is the same on both paths. The variable is the static context of the call - how much it had fixed about the type when the body was selected - and that leaves no run-time trace. You find it by reading the call sites and the visible overrides, or by adding a marker inside each body and observing which marker appears.
  • What structural change stops a specialized body from drifting again?
    Take the contract-bearing steps away from it. Keep framing, the length prefix and the checksum in a shared step no override can replace, and let the override supply only the payload layout. Then back it with one conformance suite instantiated for every specialized argument, asserting the same observable output as a general-body reference.
  • Is it ever legitimate for the two bodies to behave differently?
    Only where the difference is not observable through the contract - speed, allocation, the order of internal steps. If callers can see the difference in the output, the error, or the effects, the override is a second operation wearing the first one's name, and it should be named separately instead.

Two print shops work from the same order form, and one of them was quietly told to skip the final stamp. Which shop an order reaches is decided by who took the call, not by anything written on the form - so two identical forms come back different.

saying these in an interview costs you the question

  • Assumes a narrower body is checked against the general body's behaviour
  • Says the same value must always reach the same body
  • Blames the data rather than what the call site knew
  • Copies the checksum step into each body instead of sharing it
  • Thinks tests over the general body also cover the overrides
  • Treats a behaviour change in an override as a legitimate optimisation