skip to content

Your read path branches across four document generations at once. How do you decide what to backfill, retire, and enforce going forward?

level: principalimportance: should knowfreq 28%

answer

  1. Count documents per generation first
  2. Also ask when each was last written
  3. The cost is code, not disk
  4. Declare a floor and monitor it
  5. Each change retires the one below

basics

~20 s

Measure how many documents sit on each generation, price each surviving branch as permanent code and test surface, then set a supported-generations floor. Fund a backfill to clear everything below it, verify a zero count including consumers outside your service, and enforce the floor with monitoring so the count cannot creep back up.

solid answer

~50 s

Start with data, not opinion: count documents per generation. A branch holding half the collection is a fact of the model; a branch holding four hundred documents is code you are paying for out of habit. Then price what a branch actually costs — not storage, but a permanent multiplier on every read path change, its fixtures, its test matrix and the bug surface where an old shape meets new logic. That is what justifies funding the rewrite. Then set policy rather than doing a one-off cleanup. Declare a supported-generations floor (commonly current minus one), make every change that bumps the version ship with the backfill that retires the one below it, and monitor the minimum generation present so the floor is observable rather than aspirational. Before deleting a branch, check the consumers that never pass through your read path — extracts, sibling services, cached client copies — because they are where a retired shape resurfaces.

code

javascript · 13 lines
javascript
const FLOOR = 3; // supported: current (4) and one below

// emitted per run: population and liveness of every generation
const stats = await countByGeneration(users);
// { 1: { docs: 412, lastWrite: "2022-03-11" },
//   2: { docs: 88301, lastWrite: "2026-08-19" },   <- still being written
//   3: { docs: 1204776, lastWrite: "2026-08-20" },
//   4: { docs: 9881002, lastWrite: "2026-08-20" } }

for (const [gen, s] of Object.entries(stats)) {
  if (Number(gen) < FLOOR && s.docs > 0) alert(`below floor: gen ${gen}`);
  if (Number(gen) < FLOOR && isRecent(s.lastWrite)) alert(`writer still on gen ${gen}`);
}

go deeper

for a junior

Know that old document generations do not disappear on their own and that each one leaves branch code behind, so someone has to decide when to convert the data and delete the branch.

for a middle

Be able to explain how you would measure the problem — counts per generation and the newest write on each — and why a still-growing old generation means a writer was missed.

for a senior

Show the retirement sequence: oldest first, one at a time, gated on a verified zero count, with the branch and its fixtures actually deleted and external consumers checked before removal.

for a principal

Own the policy rather than the cleanup: a declared supported-generations floor, migrations funded as part of the change that causes them, monitoring that makes the floor observable, and a clear account of the recurring cost you are buying out.

## Why four generations happened Nobody plans this. It accumulates because each individual change made a locally sensible decision: add the branch, ship the feature, defer the backfill because it is a background job nobody is asking for. The migration was treated as optional cleanup rather than as part of the change, so it never competed successfully for attention. Recognising that pattern is half of the answer — the fix is structural, not a cleanup sprint. ## First, measure Before any judgment, get the distribution: how many documents on each generation, and when the newest document on each old generation was written. That second number matters as much as the first. A generation with ten million documents and no writes for two years is dead weight waiting for a backfill. A generation still receiving writes means some writer was never upgraded, and no amount of backfilling will converge until you find it. Also find out who reads the collection besides your service. Analytics extracts, a reporting warehouse, a sibling team's job, a mobile client holding cached copies of documents it fetched months ago. Each of those is an independent reader with its own tolerance, and a generation is not retired until they are covered too — this is usually the constraint that decides your timeline, not the backfill itself. ## Price a branch honestly The cost of an old generation is not disk. It is: - **A multiplier on every future change to the read path.** Four generations means four code paths to reason about each time the shape changes again, and the interactions grow faster than the count. - **Test matrix and fixtures.** Each generation needs a fixture and assertions, or it is untested and therefore unreliable precisely when it is rare. - **Bug surface where old shapes meet new logic.** The failures come from features written years after generation 1, by people who never saw one, meeting one. - **Decision drag.** Every engineer who touches the module must work out whether the ancient branch is still needed and, finding no answer, leaves it. That is why they never die on their own. Against that, the cost of retiring is a bounded, schedulable, one-time backfill. Framing the choice that way — permanent recurring drag versus a finite job — is what unlocks the funding. ## Set a floor, not a cleanup project The durable outcome is a policy: **the read path supports the current generation and one below it.** Anything older is a defect to be cleared, not a state to be supported. Concretely: - Any change that bumps the version ships with the backfill that retires the generation below the new floor. The migration is part of the change, reviewed with it, and the change is not done until the count is zero. - Branch removal is gated on evidence — a count of documents below the floor returning zero, repeated, plus a sign-off from the external consumers you identified. - The minimum generation present is a monitored metric with an alert, so the floor is a fact you can see rather than a paragraph in a document nobody rereads. A newest-write-timestamp per generation is the companion metric: it catches the un-upgraded writer that a count alone hides, because the count may look flat while it is quietly being replenished. ## Sequencing four generations down to two Retire from the oldest, one at a time, and prove each one before starting the next. Oldest first because it is usually the smallest population and the most fragile code, so it delivers the largest reduction in reasoning cost for the least data risk. One at a time because a single backfill converting generations 1, 2 and 3 to 4 is three transformations with one blast radius and one rollback story; three sequenced jobs each have a clean before-and-after count you can point at. Between each, actually delete the branch, the fixtures and the tests. A retirement that leaves the code in place has bought nothing — the recurring cost you were paying was the code, not the documents. ## What you must not do Do not collapse the branches into one clever tolerant reader that accepts anything. That reads as simplification and is in fact the opposite: it makes every generation permanently supported, removes the version's meaning, and guarantees the ambiguity that versioning existed to prevent. Do not delete a branch because it looks unused; a rare branch and a dead branch produce identical logs. And do not run the whole thing as an unfunded background effort — the reason there are four generations is that the last three cleanups were unfunded background efforts too. ## The judgment an interviewer is listening for That you priced the recurring cost of branches rather than the storage, that you gated removal on measured evidence rather than confidence, that you accounted for consumers outside your own read path, and above all that you changed the process so the fifth generation retires the third automatically instead of joining it.

  • An old generation has a stable document count but recent write timestamps. What does that tell you?
    That some writer was never upgraded and is still producing documents in that shape, so a backfill will never converge — it clears documents at roughly the rate they reappear. Find the writer before scheduling any migration work: it is usually an importer, an admin tool, a seed job or a sibling service that missed the rollout. Fix the source, then clear the population.
  • Why not just merge the four branches into one reader that accepts any of the shapes?
    Because it makes every generation permanently supported and strips the version field of meaning, which reinstates exactly the ambiguity versioning removed. A tolerant catch-all also loses the ability to say a shape is retired, so the cleanup can never complete and the next change has to reason about all four shapes anyway. Retire generations; do not paper over them.
  • How do you keep a fifth generation from simply joining the pile?
    Make retirement part of the change that creates it: any version bump ships with the backfill clearing the generation that falls below the supported floor, reviewed in the same change and not considered done until the count is zero. Back it with a monitored minimum-generation metric and an alert, so the policy is observable rather than a document nobody rereads.

saying these in an interview costs you the question

  • Judges which branches to keep by opinion rather than counts
  • Prices old generations by storage instead of code and test cost
  • Collapses all generations into one permissive catch-all reader
  • Deletes a branch because logs show no recent errors
  • Ignores extracts and sibling services that read documents directly

context