Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Backward Compatibility
HLD

Backward Compatibility

New code reading old data — the default compatibility mode and how to achieve it in practice.

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

ArtifactBackward compat burden
Database rowsNew code reads all history — expand/contract migrations
Event streamsNew consumers replay old topics — registry-enforced
HTTP responsesNew clients meet old servers — forward (next lesson)
CachesMixed 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.

My Private Notes

Notes are auto-saved locally to this device.