The Definition
BACKWARD COMPATIBLE change:
new version of the READER/CODE handles data produced
by the OLD version
old producer ──► [ stored data / old messages ] ──► new consumer ✓
why it's the default mode: code deploys first (rolling updates),
but data/messages persist. the NEW code must immediately cope
with everything OLD that still exists — databases, queues,
caches full of yesterday's shape
Achieving It: Writer Rules
producers/writers keep changes ADDITIVE:
✓ add new optional fields (new tag numbers in protobuf)
✓ add new enum VALUES only if consumers are tolerant-readers
✓ widen numeric types where encoding allows
✗ remove fields → old data has them; readers may need them
✗ rename → same as remove+add
✗ make optional required → old data lacks it; validation fails
protobuf makes this mechanical: field NUMBERS are forever.
JSON needs discipline: never delete keys, deprecate-in-place instead
Achieving It: Reader Rules
tolerant reader pattern on the consuming side:
- unknown fields → ignore, don't fail deserialization
- missing fields → sensible default or explicit absence handling
- unknown enums → "UNRECOGNIZED" bucket, not crash
- type surprises → coerce safely or fail loudly at boundary
consequence: writers may add anything; readers survive.
this single convention unlocks most safe evolution
Worked Examples
example 1 — additive (safe):
v1 record: {id, email}
v2 writer: {id, email, phone?}
v2 reader of v1 record: phone absent → null ✓ backward compatible
example 2 — removal done right:
want to drop legacy_name
1. stop writing it (readers keep tolerating presence)
2. readers switch to display_name exclusively
3. observe legacy_name reads ≈ 0
4. purge from storage via migration job
removal is a PROJECT with telemetry, not a commit
example 3 — enum addition:
status: active|cancelled + new "refunded"
strict-switch consumers break on "refunded"
→ tolerant readers required BEFORE adding values
(compatibility is a property of BOTH ends)
Where Each Side Lives
| Artifact | Backward compat burden |
|---|---|
| Database rows | New code reads all history — expand/contract migrations |
| Event streams | New consumers replay old topics — registry-enforced |
| HTTP responses | New clients meet old servers — forward (next lesson) |
| Caches | Mixed versions post-deploy — tolerate both briefly |
Enforcement Not Hope
CI gates that make compatibility testable:
- schema diff tool flags breaking changes (removals, renumbering)
- golden-file tests: deserialize corpus of OLD payloads with NEW code
- registry rejects incompatible publishes to protected topics
compatibility enforced in pipelines survives team turnover;
compatibility as documentation rots instantly
Interview Framing
Distinguish the direction crisply when asked (“backward = new reads old”), then show both halves: disciplined writes AND tolerant reads, plus CI enforcement. The golden-test mention — replaying historical payloads against new code — is a concrete practice that separates practitioners from terminology reciters.
Premium Content
Unlock Backward Compatibility and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans