skip to content

Document Data Modeling

The paradigm-level craft of shaping documents: what to embed, what to reference, how much duplication you can afford, and how a 'schemaless' store still evolves safely. Every document-database interview eventually lands here, whatever the vendor.

on this pageshow

questions

28

A product's name is copied into every order document. What breaks when that product is renamed?

level: juniorimportance: must knowfreq 68%

answer

  1. the database is not watching the copy
  2. nothing errors when they disagree
  3. ask who owns the update
  4. sometimes the right answer is 'never update it'
  5. name the field for its intent

basics

~20 s

Nothing updates the copies automatically: old order documents keep the old name until some process rewrites them. Either the write path owns propagation, or the copy is deliberately frozen as a historical snapshot and named to say so.

solid answer

~50 s

A document store treats the copied name as ordinary data — there is no constraint linking it back to the product record, so renaming the product changes exactly one document and leaves every order untouched. Nothing errors; screens simply disagree, which is why the bug usually surfaces weeks later. There are only three legitimate owners of that copy: the write path updates source and copies together, an asynchronous propagator rewrites the copies within a bounded lag, or nobody updates it because the copy is intentionally a point-in-time snapshot. The third is often the right answer for an order, and then the field should be called something like `productNameAtPurchase` so no future reader mistakes it for a stale cache. Whichever you choose, you must be able to *find* the copies — that usually means indexing the source id inside the order document.

code

json · 15 lines
json
// Copy with sync debt: must be rewritten whenever the product is renamed
{
  "_id": "order-9001",
  "productId": "prod-42",
  "productName": "Blue Widget",
  "qty": 2
}

// Frozen snapshot: correct forever, nobody should backfill it
{
  "_id": "order-9001",
  "productId": "prod-42",
  "productNameAtPurchase": "Blue Widget",
  "qty": 2
}

go deeper

for a junior

Be ready to say plainly that copied fields are not updated by the database, and that the order documents keep the old name until code rewrites them. Knowing that nothing errors is half the answer.

for a middle

Explain the three possible owners of a duplicated field — write path, background propagator, or deliberately nobody — and why the order document also needs the product id, indexed, before any propagation is possible.

for a senior

Show the operational side: an idempotent resumable backfill, a stored source version so drift is detectable, and a sweep that reports divergence. Argue for freezing the copy where the business fact is genuinely point-in-time.

for a principal

Own the rule for the codebase: every duplicated field is registered with an owner and a maximum staleness, or it is not allowed. Talk about how you keep that rule enforceable as more services learn to write these documents.

## What a copied field actually is When an order document stores `productName: "Blue Widget"`, that string is a copy of a fact whose home is the product document. The database has no idea the two are related. There is no foreign key, no cascade, no trigger implied by the model. The copy is inert bytes that happened to be correct at the moment they were written. Everything about keeping it correct afterwards is an application decision, and if nobody makes that decision explicitly, the default is "the copy is never updated". ## What actually happens on a rename Rename the product and exactly one document changes. Every order document still holds the old string. No read fails, no write is rejected, no log line appears. The order list rendered from order documents alone shows the old name; an admin report that reads the product document shows the new one; a search index rebuilt from orders shows something else again. The system is now internally inconsistent in a way that is invisible until a human notices two screens disagreeing. That silence is what makes duplication debt dangerous compared with a missing field or a type error, which fail loudly on the first read. ## The three possible owners of a copy Every duplicated field must have exactly one answer to "who updates this?": 1. **The write path.** The operation that renames the product also rewrites the copies. This is cheap only when the copies are few and easy to locate, and it makes the rename latency proportional to the number of documents holding the copy. 2. **An asynchronous propagator.** The rename is recorded, and a background consumer or scheduled job rewrites the affected documents in batches. Copies become correct *eventually*, and the design owes you a bound on how long that takes plus monitoring of the actual lag. 3. **Nobody, deliberately.** The copy is a historical snapshot: the order should show the product as it was named when the customer bought it. Then a rename *must not* propagate, and the correct fix is naming — `productNameAtPurchase`, `nameAtOrderTime` — so a future maintainer does not "fix" it by writing a backfill. The weak interview answer is to assume the database handles it. The second-weakest is to pick option 3 without saying so, which is indistinguishable from a bug. ## You must be able to find the copies Propagation only works if you can locate the documents that hold the copy. That means the order document should also carry the product's identifier, and that identifier should be indexed, so "all orders referencing product X" is a targeted query rather than a full collection scan. Storing only the denormalized name and not the id is a common and painful mistake: the copies become unfindable except by matching on the stale string itself, which is exactly the value you are trying to change. ## Partial failure and repair Rewriting many documents is not one atomic act. A propagation job that dies halfway leaves some orders with the new name and some with the old, so the job must be safely re-runnable: filter on documents that still hold the old value, or on a stored source version, and process in resumable batches. Carrying the source record's version or last-updated timestamp inside the copy is what makes divergence *detectable* — a periodic sweep can compare the copy's recorded source version against the product's current one and report or repair the mismatches. Without such a marker, the only audit available is re-reading every source document, which is why teams often discover drift only through customer complaints. ## Choosing not to duplicate The copy exists to avoid a second read. If the product name is edited often, is shown in only a few places, and those places already load other product data, dropping the copy and reading the product document is frequently the better trade. Duplication buys read locality and costs write-time work plus a permanent correctness obligation; you should be able to say what the read actually saved before you accept that obligation. ## How to answer this in an interview Say what breaks (silent divergence, not an error), name the three possible owners, pick one for the scenario and justify it, and mention the two operational requirements: a way to find the copies, and a way to detect divergence. Mentioning that an order's product name is often *correctly* frozen turns a rote answer into a modelling answer.

  • How would you find every order document that holds the stale name?
    Query on the product's identifier, not the name — which means the order document must also store `productId` and that field must be indexed. If you stored only the denormalized string, the copies are findable only by matching the stale value itself, and any order that already drifted for another reason is missed. This is why an embedded copy should always travel with the id of the record it came from.
  • How do you make a propagation job safe to re-run after it crashes halfway?
    Make it idempotent and resumable: select only documents that still carry the old value or an older source version, process in bounded batches, and record progress so a restart continues rather than starting over. Because the rewrite spans many documents it is never atomic, so partial completion is the normal state, not the exception, and the job must converge when run repeatedly.
  • When is it correct for the copy to stay stale forever?
    When the copy is a point-in-time fact rather than a cache of a mutable one. An order should show the product name, price and tax rate as they were at purchase, because that is what the customer agreed to and what an invoice must reproduce. Signal the intent in the field name so nobody backfills it, and keep the reference id alongside for lookups of the current record.

saying these in an interview costs you the question

  • Assumes the database cascades the change to embedded copies
  • Stores the copied name without the source id
  • Treats every stale copy as a bug rather than sometimes a snapshot
  • Writes a one-shot fix script with no re-run safety
  • Says duplication is simply wrong instead of asking who maintains it

context

open as a page

When should you embed related data in a document and when should you store a reference?

level: juniorimportance: must knowfreq 85%

basics

~10 s

Embed when the related data is bounded in size, read with its parent, and changes with it. Reference when it is unbounded, shared by many parents, edited independently, or queried on its own.

open as a page

What does calling a document database "schemaless" actually mean for your application?

level: juniorimportance: must knowfreq 78%

basics

~20 s

It means the database does not check a document's shape when you write it — not that there is no schema. The schema still exists, implicitly, in the code that writes and reads documents. Enforcement moved; it did not disappear.

open as a page

Why is a single document the consistency scope in a document database, and how does that shape aggregate boundaries?

level: middleimportance: must knowfreq 62%

basics

~20 s

A write to one document lands entirely or not at all, so one document is the widest scope where an invariant can be enforced for free. Any rule that must always hold should sit inside a single aggregate; rules spanning documents need extra machinery or must tolerate lag.

open as a page

What does query-first, or 'design for the read', modelling mean in a document database?

level: middleimportance: must knowfreq 70%

basics

~20 s

Query-first modelling starts from the list of operations the application must serve and shapes documents so each one is answered by a simple, well-targeted access. The document shape follows the workload, not a diagram of entities.

open as a page

In document data modeling, what is fan-out on write versus fan-out on read, and how do you choose?

level: middleimportance: must knowfreq 62%

basics

~20 s

Fan-out on write copies a fact into every document that will need it at write time, so reads are single lookups. Fan-out on read stores it once and gathers it from many sources per read. Choose by read-to-write ratio and fan-out size.

open as a page

Why do unbounded embedded arrays eventually break a document-database design?

level: middleimportance: must knowfreq 66%

basics

~20 s

An array with no ceiling eventually pushes the document past the store's hard size limit, and long before that every read and every update of the parent pays for the whole array. Move those children into their own records.

open as a page

In a document database, why do teams store a schemaVersion field in each document, and how does the read path use it?

level: middleimportance: must knowfreq 65%

basics

~20 s

It records which shape generation a document was written with. With no ALTER TABLE, one collection holds several generations at once, so the stored version lets read code branch deterministically instead of inferring shape from which fields happen to be present.

open as a page

In a document store, how do you choose between migrating documents lazily on read and running a background backfill?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Lazy migration converts a document when application code touches it, so cold records never convert and branch code lives forever. A backfill sweeps the whole collection and gives a finish line. Most real migrations ship the tolerant reader first, then backfill, then delete the branch.

open as a page

How do you decide whether to enforce document shape in the database or in application code?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Enforce the coarse contract in the database — required fields, types, allowed discriminator values — because it catches every writer including scripts and migrations. Enforce semantics and cross-field business rules in application code. Use both layers; they cover different failure modes.

open as a page

In document data modelling, what makes a document an aggregate rather than just a stored record?

level: juniorimportance: should knowfreq 50%

basics

~20 s

An aggregate is the clump of data you load, write and keep consistent as one unit. A document is an aggregate when its contents are always used together and nothing outside it must change atomically with it.

open as a page

Why is the price paid stored on an order document not the same kind of duplication as a copied product name?

level: middleimportance: should knowfreq 48%

basics

~20 s

The price paid is a distinct historical fact the order owns, so it must never track the product's current price. A copied product name is a cache of a mutable fact elsewhere and carries a synchronisation obligation. Only the second is duplication debt.

open as a page

What does referencing instead of embedding cost you in write atomicity?

level: middleimportance: should knowfreq 50%

basics

~20 s

A write to one document is atomic; a write spanning two is not. Once related data is referenced, an update can be observed or left half-applied, unless you pay for a multi-document transaction or design the drift away.

open as a page

When you introduce a schemaVersion field into a live collection, how do you handle documents written before it existed?

level: middleimportance: should knowfreq 42%

basics

~20 s

Treat an absent version field as the oldest known generation by convention, rather than rewriting every document just to stamp a number on it. Read code normalizes missing to 1, and migration progress is counted as documents that are missing the field or below the current generation.

open as a page

In a document store, when should a field be absent versus stored as an explicit null?

level: middleimportance: should knowfreq 45%

basics

~20 s

Absent usually means the fact was never recorded or does not apply; explicit null means it was recorded as deliberately empty. Document stores distinguish the two, so pick one convention per field and apply it everywhere rather than letting both appear.

open as a page

In a document database, when should documents of different shapes share one collection?

level: middleimportance: should knowfreq 55%

basics

~20 s

Keep different shapes together when they are read by the same queries, share a common core of fields, and need the same indexes. Split them when access patterns, growth rates or retention differ. Always carry an explicit type field to tell variants apart.

open as a page

Once data is split into separate aggregates, what does an operation spanning two of them actually cost?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Crossing an aggregate boundary costs an extra access the application must issue and order, no automatic all-or-nothing across the two, and no engine-enforced referential integrity. The usual failures are N+1 read patterns and half-finished multi-document writes.

open as a page

Your hot read path and hot write path want different document shapes — how do you draw the aggregate boundary?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Measure both paths, then let the dominant one own the primary aggregate and serve the other from a derived shape or extra accesses. Boundaries protect invariants first; beyond that, the heavier and more latency-sensitive path wins.

open as a page

How do you keep a precomputed count stored inside a document from drifting away from the rows it summarises?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Increment it atomically within its own document, make the increment idempotent so retries cannot double-count, store a marker of what has been counted, and run a periodic recompute from the source that detects and repairs drift instead of assuming none.

open as a page

How do you decide how stale a duplicated field in your documents is allowed to be, and how do you enforce that bound?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Classify each duplicated field by what a wrong read costs: display-only fields tolerate seconds or minutes, while money, permissions and inventory tolerate nothing and must be read from the source. Then pick a propagation mechanism whose measured lag meets that bound, and add a sweep that caps the worst case.

open as a page

In a many-to-many relationship, which side should hold the array of references?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Put the array on the side whose list is genuinely bounded, and never on a side that can grow without limit. When both sides are unbounded, or the relationship carries its own attributes, give it a collection of link documents.

open as a page

In document modelling, when should a reference also carry a copy of a few fields?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Copy a field next to its reference when the hot read needs only that field and resolving the whole referenced document would cost an extra round trip. Prefer fields that are immutable or that record a point-in-time value.

open as a page

What must be true of a document shape change so you can safely roll the application release back after deploying it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The previous release must still be able to read documents the new release wrote. That means shipping the tolerant reader before the new writer, keeping the old fields populated while both releases can run, and delaying any destructive rewrite until rollback is off the table.

open as a page

How does the tolerant reader principle keep a document consumer from breaking when writers change?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A tolerant reader takes only the fields it needs, ignores everything it does not recognise, and treats a missing field as a defined default. That makes additive changes by writers safe, so producers can evolve without coordinating a release with every consumer.

open as a page

How would you choose aggregate boundaries for a new document-database system when the access patterns are still uncertain?

level: principalimportance: should knowfreq 38%

basics

~20 s

Anchor the boundaries on what is stable — the invariants and the units the business writes as a whole — keep aggregates modestly sized, avoid unbounded nesting, and treat everything derived as replaceable so shapes can change as real access patterns appear.

open as a page

A field is duplicated into millions of documents across six collections and must now become editable. How do you decide whether to keep duplicating it?

level: principalimportance: should knowfreq 36%

basics

~20 s

Measure what the duplication actually buys on the read path, then count the maintenance surface: how many writers can create these documents, how often the field changes, and whether the copies are even findable. Keep duplication only where the read win survives an honest edit and repair cost.

open as a page

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%

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.

open as a page

How do you govern schema flexibility when many teams write to the same document collections?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Give each collection a single owning team that publishes its core contract, allow additive change freely, gate anything non-additive, back the contract with a narrow write-time check, and measure real drift by sampling stored documents rather than trusting the code.

open as a page