skip to content

Schema Evolution and Compatibility

How a wire contract changes over time without breaking readers and writers that upgrade at different speeds. A staple of senior interviews: every long-lived system eventually lives or dies by it.

part ofData serializationoverview, primer and where to startread it →
on this pageshow

questions

21

A wire schema gains a new version: what does backward compatibility guarantee about readers and data, and how does forward compatibility differ?

level: middleimportance: must knowfreq 78%

answer

  1. always ask: which side reads?
  2. a direction in time, not a rank
  3. one guarantee per version pair
  4. new code meeting older records
  5. old code meeting newer records

basics

~20 s

Backward compatibility means a reader on the new schema can decode data written under the old one. Forward compatibility is the mirror image: a reader still on the old schema can decode data written under the new one.

solid answer

~40 s

Both names describe the **reader**, and the direction in time it has to cope with. **Backward** compatible means a reader using version `N` can decode records a writer produced under version `N-1` — new code reaching backwards at older data. **Forward** compatible means a reader still on `N-1` can decode records a writer produced under `N` — old code coping with data from its future. **Full** compatibility is both directions holding for the same pair of versions. The payoff is deployment freedom: whichever direction holds tells you which side of the fleet may move first while both versions run side by side. The guarantee is always relative to a named pair of versions, never an absolute property of one schema.

go deeper

for a junior

Learn the two terms as a pair and always attach them to the reader: one describes new code meeting older records, the other old code meeting newer records. Mixing them up is the single most common slip in this material.

for a middle

Explain that the guarantee is defined over a version pair plus a direction, derive full compatibility as the conjunction of the two, and show that a change can satisfy one direction while violating the other.

for a senior

Show you have used the guarantee operationally: name the direction that holds and say which side of a fleet you may therefore upgrade first while both versions run together for days.

for a principal

Frame the guarantee as a tax levied on schema authors in exchange for removing cross-team deployment coordination, and be ready to say which channels are worth taxing at which strength.

## What the two words are actually about A **compatibility guarantee** is a statement about one decode. A **reader** running some version of a schema is handed bytes that a **writer** produced under some other version, and the guarantee says whether that decode is defined. Three things must be named before the statement means anything: the reader's version, the writer's version, and which of the two is older. Candidates who mix the two terms up almost always do so because they attach the word to the *change* or to *whoever deployed*, rather than to the reader. The convention is consistent wherever the terms are used: - **Backward compatible** — a reader on the **newer** schema can decode data written under an **older** one. The new code reaches backwards in time. - **Forward compatible** — a reader on the **older** schema can decode data written under a **newer** one. The old code has to cope with data from its future. - **Full compatible** — both of the above hold, for the same pair of versions. A useful discipline: say the sentence out loud with both versions in it. "A version 5 reader decodes version 4 data" is unambiguous even if you have forgotten which label it carries; the label is just shorthand for that sentence. ## The pair, not the schema Compatibility is a **relation over a pair**, not a property of a single artifact. "Version 5 is backward compatible" is an incomplete claim in the same way that "this number is larger" is incomplete. Version 5 may be backward compatible with version 4 and not with version 2; the two statements are independent, because each concerns a different set of bytes arriving at the same decoder. This matters in three concrete ways: 1. A claim that names no second version cannot be checked, so it cannot be relied on. 2. Two changes that are each compatible with their predecessor can compose into a pair that is not — the stronger, history-wide claim is a separate and strictly stronger guarantee. 3. A request type and a response type between the same two services are two different contracts with two different readers, so they carry their own guarantees independently. ## The table to keep in your head | Guarantee | Reader's version | Data's version | What it buys you | |---|---|---|---| | Backward | newer | older | readers may be upgraded ahead of writers | | Forward | older | newer | writers may be upgraded ahead of readers | | Full | either | either | no upgrade ordering constraint at all | Read the middle two columns first and the label falls out. If the reader is newer than the data, you are talking about backward compatibility. If the reader is older than the data, you are talking about forward compatibility. If you cannot say which is newer, the question has not been posed properly yet. ## Why an interviewer asks this The question is not vocabulary for its own sake. During any rolling change there is a window — minutes for a small service, days for a fleet of a few hundred consumers and a handful of producers — in which both versions are live simultaneously. In that window, four writer/reader pairings occur. Two of them are same-version pairings that were already working. The other two are the cross pairings, and **each direction of compatibility covers exactly one of them**. So the direction that holds is the same fact as the deployment order you are allowed to use, stated a different way. An engineer who can move fluently between "this change is backward compatible" and "therefore consumers go first" has done the rollout; one who can only recite the definitions has not. ## Where the guarantee stops These terms are narrower than they sound, and the boundaries are worth stating explicitly: - They are about **decoding**, not meaning. A field silently repurposed to carry a different quantity under the same name decodes cleanly in both directions and still corrupts everything downstream. No compatibility check catches that, because nothing about the bytes changed shape. - They cover one **version pair**, not a history. Extending the claim over every past version is the transitive form, and it does not follow from the per-step checks. - They say nothing about **which concrete edits** satisfy each direction. That is a separate catalogue, and the answer differs between families of encodings. - Ecosystems genuinely differ in how they express the rules — some resolve a reader's schema against the writer's at decode time, others match on field identifiers alone — but the direction being named is the same in all of them, which is why the terms travel. ## The habit that prevents the mix-up When the label slips, rebuild it from scratch in three steps: name the reader's version; name the version the bytes were written under; ask which is older. Reader newer than the data means backward. Reader older than the data means forward. Both required at once means full, and full is the claim you make when nothing lets you control which side moves first.

  • Is compatibility a property of a schema on its own?
    No. It is a property of a **pair** — the reader's version and the version the data was written under — together with a direction. One schema can be backward compatible with the version immediately before it and incompatible with one three releases back, so a compatibility claim that does not name both versions is not yet a claim you can check.
  • What does full compatibility buy that a single direction does not?
    It removes deployment ordering entirely. Readers and writers on either version can be mixed in any proportion and any sequence, which is what you need when you cannot control the order: many independent consumer teams, data that outlives the release, or a request and response pair where both sides move on their own schedule.
  • Where does a compatibility guarantee stop protecting you?
    At the boundary of decoding. The guarantee says a reader can parse the bytes and populate its own model; it says nothing about whether the meaning survived. A field quietly repurposed to carry a different quantity under the same name satisfies both directions and still produces wrong numbers in every consumer that reads it.

A clerk trained on this year's form who can still process last year's submissions is reading backwards in time, while a clerk still trained on last year's form who is handed this year's submissions must cope with its future. The label always names which way the reader is looking, never which version of the form is better.

saying these in an interview costs you the question

  • Thinks backward compatibility means old code reading new data
  • Calls a schema compatible without naming which two versions
  • Believes forward compatibility is just backward compatibility restated
  • Assumes any change that still decodes has preserved the meaning
  • Treats full compatibility as a free default rather than a cost
open as a page

When a central schema registry rejects a new version of a record schema, what has it actually compared?

level: middleimportance: must knowfreq 62%

basics

~20 s

The registry compares the submitted schema document against the stored versions of one named lineage, in the direction that lineage's compatibility mode requires. It is a document-to-document check made at registration time, not an inspection of deployed readers or code.

open as a page

In a shared schema, why is adding an optional field with a default safe for old readers, while promoting a field to required is not?

level: middleimportance: must knowfreq 66%

basics

~20 s

Adding an optional field only asks an old reader to skip bytes it never needed, and its default gives new readers a value when old writers omit it. Making a field required invalidates every message already written without it.

open as a page

Why can renaming a field be a non-event in one schema family and a breaking change in another, given the same wire bytes?

level: middleimportance: must knowfreq 70%

basics

~20 s

Identity decides. Where a reader matches fields by number, a rename changes only the label. Where it matches by name, a rename is a delete plus an add, so readers on the old schema quietly stop finding the value.

open as a page

A service must mark which version of its payload shape a message carries. Where can that version identifier live, and what does each placement cost?

level: middleimportance: must knowfreq 68%

basics

~20 s

Three placements are common: a version field inside the payload, a negotiated media type, and a version segment in the address. Each moves the cost elsewhere - routing and caching, client effort, or resource identity - and none removes it.

open as a page

Several hundred consumers and a handful of producers must move to a new schema over days: which side upgrades first?

level: seniorimportance: must knowfreq 65%

basics

~20 s

The direction that holds decides the order. A backward-compatible change lets the readers go first, since new readers cope with data still written under the old schema; a forward-compatible change lets the writers go first. Full compatibility means any order works.

open as a page

A platform team says its central schema registry enforces compatibility, yet a breaking record-schema change reached consumers. What must hold for the registry to gate a publish?

level: seniorimportance: must knowfreq 66%

basics

~20 s

A registry gates a publish only when the decision is server-side, the name being published to has a mode configured, no write path reaches the bytes without registering, and the mode itself is not freely editable by the team being gated. Miss any one and it is a library.

open as a page

A live endpoint must replace a field with an incompatible one while old callers keep sending the old shape. What does an expand-and-contract migration do in each phase?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Expand-and-contract ships one incompatible change as a sequence of compatible ones: add the new form beside the old, write both, move readers to the new with a fallback, backfill what already exists, stop writing the old, then remove it.

open as a page

Two services exchange a request and a response and each deploys on its own schedule: why is one compatibility direction not enough?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Each side writes on one hop and reads on the other, so a single deployment imposes opposite requirements at once: an old callee must read new requests while a new caller must read old responses. Only both directions cover both hops.

open as a page

In a pipeline whose clients resolve schemas from a central registry, what determines whether a registry outage stops traffic?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Whether clients still need a resolution they have not already cached. Warm clients publishing and reading known schema versions keep running; cold starts, a first sighting of a new version, and register-on-publish producers all need the registry and stall or fail without it.

open as a page

Which changes to a scalar field's declared type survive a mixed-version rollout, and which corrupt values without raising an error?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Only a widening whose on-wire byte form is unchanged survives, and only while writers stay inside the old range. Narrowing, signed-to-unsigned reinterpretation and any edit that changes the byte form corrupt values silently rather than failing.

open as a page

When a field is deleted from a tag-numbered schema, why must its number and its name be reserved rather than freed for reuse?

level: seniorimportance: should knowfreq 60%

basics

~20 s

A retired number and name still live in deployed readers and in bytes already written. Reserving them stops a later edit from handing an old reader a different field under an identifier it already believes it understands.

open as a page

Your team keeps two versions of one endpoint's payload running side by side for a year. What actually doubles, and what does not fork?

level: seniorimportance: should knowfreq 46%

basics

~20 s

What doubles is the surface: contracts, tests, docs, client support and every later change, which must land in both. What does not fork is the data and the behaviour behind them, so a change of meaning reaches old callers however many shapes you serve.

open as a page

Which compatibility guarantee would you require for a long-retention event stream versus a short-lived internal channel, and why?

level: principalimportance: should knowfreq 38%

basics

~20 s

Match the guarantee to how long data and stragglers outlive a release. A long-retention stream needs a transitive guarantee so current readers can replay all of history; a short-lived channel drained between releases can run honestly on the weaker per-step form.

open as a page

Any team in your organisation can relax its own schema name's compatibility mode to unblock a release. What does that autonomy cost?

level: principalimportance: should knowfreq 38%

basics

~20 s

The guarantee stops being a property of the platform and becomes a per-name, per-day unknown. Consumers can no longer reason about what is enforced anywhere, the cost of an override lands on teams who never made it, and the evidence usually disappears with the setting.

open as a page

When a shared schema's field must change type, how do you decide between editing it in place and adding a replacement field beside the retired original?

level: principalimportance: should knowfreq 38%

basics

~20 s

Price a one-time risk against a permanent cost. An in-place edit is available only when the layout is unchanged, the direction is safe and the readers are enumerable; a replacement field burns identifiers and adds precedence logic forever, but makes the ambiguity visible per message.

open as a page

As the lead, how do you set the deprecation window for a retired payload version, signal it, and decide when it is safe to switch off?

level: principalimportance: should knowfreq 36%

basics

~20 s

Set the window from the caller population's slowest realistic cycle, not from habit; signal it both machine-readably in responses and through a human channel; and decide the shut-off from per-caller usage measured over a full cycle, escalating through warnings and brownouts rather than a single date.

open as a page

What does the transitive form of a compatibility guarantee quantify over that the plain form does not?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Transitive compatibility quantifies over every earlier version, not just the immediately preceding one. Plain per-step checks do not compose: version 3 can pass against version 2, and version 2 against version 1, while a version 3 reader still fails on version 1 data.

open as a page

Several record schemas in a registry reference one shared type by name and version. What does changing that shared type require?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

A new version of the shared type, then a re-registration of every referencing schema that is to adopt it — each re-checked against its own history. A reference pins a version, so nothing moves until its owner moves it, and the shared type's own verdict says nothing about the dependants.

open as a page

What happens when a writer starts sending an enumerated value that a reader on the earlier schema has no name for?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

The value usually arrives intact, so the decode is not where it breaks. It breaks in application logic that branches over the members it knows and has no arm for this one — an additive schema edit that is not additive for consumers.

open as a page

A payload version travels on a queue and into archived files, where no request-and-response exchange exists. Which versioning strategy still works, and why?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

Only a self-carried marker works: the version must sit in the bytes, because negotiation needs a counterpart to ask and an address to ask it at, and a consumer reading a queued message or an archived file has neither.

open as a page