Design & Architecture

Backward Compatibility & Versioning

Intermediate

As soon as something depends on your interface — an API consumer, an old app version, a queued message, a stored record — you have made a promise about its shape. Break that promise and you break them, often silently and far away. Evolve interfaces additively, version on purpose, and never strand a consumer in the middle of a change.

Compatibility is not only about public APIs. The same discipline applies to database schemas during a rolling deploy, the format of messages on a queue, persisted documents, and events other services consume. In every case there is a period where producers and consumers run different versions at once, and the contract must keep working across that gap.

The reliable strategy is expand/contract. Add the new thing, support old and new together while everyone migrates, then remove the old thing in a later step. Never change and remove in one move. Combine this with tolerant readers (ignore unknown fields, do not assume optional ones) and clear versioning when a real break is unavoidable. This lets the system evolve continuously without downtime or stranded consumers.

Evolve without breaking

Version deliberately when you must break

Repurpose a field in place // v1: "status" was a string "active"/"closed"
// v2: changed "status" to a number 0/1/2 in the same field

Every existing consumer that read the string now gets a number and breaks. Old stored records no longer match the new readers. This is a silent, system-wide break that looks like a small change.

Add, migrate, then remove // add a new field, keep the old one working
{ "status": "active", "statusCode": 0 }
// once all consumers read statusCode (and old records are backfilled), drop status

Old and new consumers both work throughout. Nothing is stranded. The old field is removed only when nothing depends on it.

Self-review checklist

Why it matters: Breaking changes fail far away and often silently. An integration partner, a mobile app already released, or an old message on a queue stops working, with no error on your side. Additive evolution and deliberate versioning let the platform change continuously without downtime, stranded consumers, or the 2 a.m. discovery that a 'small' field change broke production.