How does the tolerant reader principle keep a document consumer from breaking when writers change?
answer
- read only what you need
- ignore fields you do not recognise
- additive changes must not break you
- whole-document write-back erases unknown fields
- tolerance does not survive a rename
basics
~20 sA 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.
solid answer
~50 sThe principle is: **be strict in what you write, tolerant in what you read.** A tolerant reader extracts the specific fields it uses, never asserts that the document contains nothing else, and has a defined behaviour for a field that is absent. Under that discipline an additive change — a writer starts including a new field — cannot break any existing consumer, which is what makes independent deployment possible in a schema-on-read store. The intolerant patterns are the ones to name: deserializers configured to throw on unknown properties, code that asserts an exact field count or shape, and above all **read-modify-write of the whole document**, which silently erases fields a newer writer added but this reader does not know about. Tolerance has a limit, though: it makes *additive* change safe. Renames, removals and type changes still break readers, and no amount of reader tolerance fixes that.
code
javascript · 8 lines// intolerant: replaces the whole document, dropping unknown fields
const doc = await store.findById(id); // type knows four fields
doc.status = "PAID";
await store.replace(id, doc); // a fifth field written by
// another service is gone
// tolerant: change only what you own
await store.updateFields(id, { status: "PAID", paidAt: now });go deeper
Know the slogan and what it implies in code: pull out the fields you need and do not fail because the document contains something extra.
Explain why tolerance enables independent deployment in a store with no write-time check, and name the intolerant defaults — strict deserialization and whole-document validation — that quietly undo it.
Bring up the lossy read-modify-write: replacing a whole document silently deletes fields another writer added. Describe the field-level update or unmapped-key capture that prevents it, and state the ownership rule you would set.
Own the boundary: reader tolerance buys unilateral additive change and nothing else, so the organisation still needs a rule that renames, removals and type changes are coordinated.
## The principle "Be conservative in what you send, liberal in what you accept" is old advice from protocol design, and it applies directly to documents. Concretely, a tolerant reader: - reads **only the fields it actually uses**, by name; - **ignores unknown fields** rather than failing on them; - has a **defined behaviour for a missing field** — a default, a skip, or an explicit domain error, decided on purpose rather than by whatever the mapping layer does; - **does not assume field order or exhaustiveness**; - validates the subset it consumes, and nothing else. The payoff is that a writer can add a field without asking every consumer for permission. In a store that performs no write-time shape check, that is the difference between independent deployment and a coordinated release train. ## Where readers are accidentally intolerant Most intolerance is not a decision; it is a default nobody looked at. **Deserializers that fail on unknown properties.** Many object mappers can be configured either way. If the strict setting is on, a producer adding one field takes the consumer down — a failure that is maximally confusing because the consumer did not change. **Whole-document validation.** Running the document through a strict shape check that forbids anything not listed makes the consumer as brittle as the strictest schema anyone wrote. Validate the fields you consume, not the document. **Structural assumptions.** Asserting a field count, indexing into a document positionally, or assuming a sub-object contains exactly two keys. **Read-modify-write of the whole document.** This is the most damaging one and deserves its own section. ## The lossy write-back trap A service loads the whole document into its own type, changes one field, and writes the whole object back. Any field that the writer's type does not know about — because a newer producer added it — is not in the object, and so is not in the document that gets written. The field is silently deleted. This is insidious because both services are individually correct, nothing errors, and the loss only shows up when the *other* service next reads its own field and finds it gone. It is also intermittent: the field survives until the older service happens to touch that document. The fixes: - **Update only the fields you changed**, using a targeted field-level update, rather than replacing the document. - If you must replace the whole document, **preserve unknown fields** — many mapping layers can capture unmapped keys into a catch-all map and write them back out. - Failing both, make ownership explicit: exactly one service may replace whole documents in this collection, and everyone else does field-level updates. ## Tolerance is not a substitute for compatibility Be clear about the limit. Tolerant readers make **additive** change safe. They do nothing for: - **Renames** — the reader looks for the old name and finds nothing. From the reader's perspective a rename is a deletion plus an addition. - **Removals** — the field the reader depends on is gone, and "tolerantly" defaulting it to zero may be worse than failing loudly. - **Type changes** — a number that becomes a string will pass a presence check and then produce a wrong comparison or a parse error further in. - **Meaning changes** — the field is still there with the same type, and now means something else. This is the worst kind because no mechanical check catches it. So the rule set that actually holds a system together is: readers are tolerant, writers are conservative, and *only additive changes are made unilaterally*. Anything non-additive is a coordinated change regardless of how tolerant the readers are. ## Defaults deserve thought "Treat missing as a default" is where tolerance can hide a bug. Defaulting a missing `discountPercent` to 0 is safe. Defaulting a missing `isActive` to `true` is a decision that can enable something that was never intended. Defaulting a missing `currency` to your home currency will produce plausible, wrong money. For anything security-relevant or financial, absent should usually mean **refuse to process**, not "assume the benign value". Tolerance means not crashing on data you do not understand; it does not mean guessing at data you require. ## What interviewers listen for The strong answer states the principle in one line, gives at least one concrete intolerant pattern — ideally the lossy read-modify-write, because it shows real production experience — and then draws the boundary: tolerance buys additive freedom, nothing more. The weak answer treats "be tolerant" as a slogan and quietly implies it makes all schema change safe, or defaults every missing field without asking whether the default is a safe assumption.
- Which schema changes does reader tolerance actually make safe?Only additive ones — a writer starting to include a new field, or a new variant appearing that the reader does not handle and can skip. Renames, removals, type changes and changes of meaning all still break consumers, because the reader is looking for something that is no longer there or no longer means what it did.
- Why is defaulting every missing field not automatically the tolerant choice?Because a default is an assumption about data you do not have. Defaulting a missing discount to zero is safe; defaulting a missing active flag to true, or a missing currency to your home currency, silently produces wrong or unsafe behaviour. For anything financial or security-relevant, absent should mean refuse to process rather than assume the benign value.
- How do you detect a lossy read-modify-write before it damages production data?Look for code paths that load a whole document into a typed object and write the object back, and check whether the mapping layer captures unmapped keys. In tests, write a document containing an extra field the service does not know about, run the update path, and assert the extra field is still present afterwards.
A tolerant reader is like someone filling in a form who copies across only the boxes they need and hands the original back untouched — rather than retyping the whole form from memory and dropping every box they did not recognise.
saying these in an interview costs you the question
- Leaves the deserializer set to fail on unknown properties
- Replaces whole documents and erases fields other writers added
- Believes tolerance makes renames and removals safe
- Defaults every missing field without asking if the default is safe
- Validates the entire document instead of the fields it consumes