Which reader behaviour for unknown fields — strict rejection or lenient ignoring — would you write into a wire contract, and when?
answer
- a contract term, not a decoder setting
- strict trades availability for feedback
- lenient trades correctness for independence
- strict forces readers to upgrade first
- ignore but count as the middle position
basics
~20 sStrict rejection where a human or a tool authored the document and a misspelled field must never be silently discarded; lenient ignoring on machine-to-machine streams with many independently deployed producers. Either way, the contract states which, rather than leaving it to each decoder's configuration.
solid answer
~50 sThe real answer is that this is a **contract term with a named owner**, not a decoder setting. Left unstated, each hop inherits whichever behaviour its own tooling defaults to, and the same message is accepted at one service and rejected at the next. Having decided to state it: **strict rejection** gives immediate feedback — a misspelled or obsolete field is reported to the sender instead of vanishing — at the cost of making any producer-side field addition a breaking change, so readers must upgrade before producers emit. **Lenient ignoring** buys independent deployment and forward compatibility: a producer can add a field on its own schedule and nothing downstream fails. Its cost is symmetrical to strict's benefit — a typo is accepted with a shrug and the value never arrives. A common middle position is to ignore but count, so leniency stops being invisible.
go deeper
Know that a reader meeting a field it does not recognise either refuses the message or carries on without it, and that which one happens is a decision somebody made.
Explain both costs concretely: strict turns any added field into a breaking change, lenient turns a misspelled field into a value that silently never arrives.
Argue the choice per edge and describe the middle position — accept but count unrecognised fields — plus the migration path from counting to alerting to rejecting.
Own the term itself: name who decides it, write it into the contract so every reader agrees, and weigh a rejected message against a silently missing value for each interface.
## The decision the contract has to state Every reader does *something* with a field it does not recognise. If the wire contract does not say what, the behaviour is decided by whichever decoder each team happened to configure — and those differ. The first-order failure is not choosing strict or lenient; it is having no stated answer, so a message is accepted at one hop and rejected at the next, and the pipeline's behaviour depends on deployment history rather than design. Naming an owner for the term matters as much as the term: someone must be able to change it deliberately and tell every reader. ## Strict rejection A strict reader fails the decode when it meets a field its schema version does not define. - **What it buys.** Feedback. A misspelled field name, a field removed last quarter, or a client built against the wrong schema version produces an error the sender sees, rather than a value that silently never applied. - **What it buys, second order.** It bounds drift. Nobody can be quietly relying on a field the contract no longer contains, because such a message does not decode. - **What it costs.** Adding a field becomes a breaking change until every reader has upgraded. The rollout is ordered — readers first, producers second — and that ordering must be enforced, not hoped for. - **What it costs, second order.** It is brittle against fan-out: with many producers on different release cadences, the probability that *some* producer is ahead of *some* reader approaches one. ## Lenient ignoring A lenient reader skips what it does not recognise and carries on. - **What it buys.** Independent deployment. Producers add fields whenever they like; readers upgrade whenever they like; no coordination window is needed. - **What it costs.** Typos and stale field names are accepted in silence. The sender believes the value was delivered, the reader never saw it, and nothing anywhere reports a problem. - **What it does not do.** Leniency is about whether decoding *fails*; it says nothing about whether the bytes are *kept*. A lenient reader that re-emits the message still drops what it did not model unless it was also written to preserve. ## Comparing the two | | Strict rejection | Lenient ignoring | |---|---|---| | Misspelled field | Reported to the sender | Silently discarded | | Adding a field producer-side | Breaking until readers upgrade | Non-breaking | | Rollout coupling | Ordered: readers before producers | None required | | Suits | Authored documents, control-plane input | High-fan-out event streams | | Main hazard | Availability: a valid-looking message fails | Correctness: a value never arrives | ## The third position: ignore, but count Most mature pipelines land between the two. The reader accepts the message, and emits a counter keyed by the unrecognised field's identifier. That turns an invisible property into an observable one: an operator can see that a reader is a version behind, and a schema owner can see which fields are being ignored and by whom. It also gives a migration path — count first, alert next, reject once the count has been zero for long enough. ## Choosing per edge The choice is per interface, not per company: - **A configuration or policy document written by a person or generated by a tool** should be strict. A misspelled key in a document whose author expects it to take effect is the classic silent-failure story, and the author is present to fix it. - **A high-fan-out event stream with many independently deployed producers** should be lenient, and should preserve, so that an intermediate hop does not become the place data goes to die. - **A boundary that exists to vet what crosses it** should be strict about what it accepts and explicit about what it forwards: forwarding fields nobody reviewed defeats the boundary's purpose. - **An internal transit hop** should be lenient and preserving; it is a courier, and couriers do not open the parcels. The answer an interviewer wants is not a preference but this reasoning: state the behaviour in the contract, give it an owner, pick per edge according to whether the cost of a silent miss exceeds the cost of a rejected message, and make leniency observable so it is a choice rather than a default nobody made.
- Why does strict rejection force a rollout order?Because a strict reader fails on a field it does not define, any producer that emits the new field before readers understand it breaks them. Readers must be upgraded to know the field first, and producers may only start emitting afterwards — an ordering that has to be enforced by the release process, not assumed.
- Does lenient ignoring imply unknown fields are preserved on re-emit?No. Leniency decides only whether the decode fails. A lenient reader still builds its output from the fields it modelled, so unknown fields are dropped unless it keeps them in a side buffer. The two properties are independent and both belong in the contract.
- How would you migrate an interface from lenient to strict?Count unrecognised fields per reader and per field identifier first, so the real traffic is visible. Alert on the count while senders fix what shows up. Only once the count has stayed at zero across a full release cycle for all producers do you flip the reader to reject, and you keep the counter afterwards.
saying these in an interview costs you the question
- Treats unknown-field handling as a decoder setting, not a contract term
- Claims strict rejection is always the more professional choice
- Says lenient readers preserve the fields they ignore
- Ignores that strict rejection forces readers to upgrade before producers
- Offers one global answer rather than choosing per interface