Backward Compatibility
Backward compatibility lets new code read old data and new clients work with older servers, enabling independent deployment and safe rollback.
Definition
Backward compatibility means a new version of a system can consume data, requests, or APIs produced for an older version. In APIs, adding optional fields and endpoints is compatible; removing fields or changing types is not. In data, new code must read old records or events. In distributed systems, compatibility allows old and new versions to run at the same time during a rolling deployment or rollback.
Context and scope
Backward compatibility is essential for independent service deployment, mobile clients that cannot update instantly, event replay, and database migrations. It is a property of a change, not a permanent guarantee for every change.
Common misconceptions
- Backward compatibility means never changing anything — additive changes and versioned deprecations are compatible
- If clients update automatically, compatibility does not matter — mobile and third-party clients update slowly or never
- A new API version makes the old one irrelevant — old versions still need security fixes and a deprecation plan
Anti-patterns
- Removing a response field without a deprecation period
- Changing a field from optional to required in the same version
- Deploying a schema change that old application instances cannot read
Related concepts
Related examples
Authoritative source
Compatibility is about mixed versions
During a rolling deploy, old and new instances run together. A mobile client may use an API version for months. An event replay may process records written years ago. Backward compatibility ensures the new code can handle the old shape and the old code can survive the new one, depending on the direction required.
type User = { id: string; name: string; avatarUrl?: string };
function render(user: User) {
return user.avatarUrl ?? '/default-avatar.png';
}
The optional field and default keep old payloads renderable. Removing name or changing id to a number would break existing clients.
Add, deprecate, remove
Prefer additive changes: new optional fields, new endpoints, new event types. Announce deprecations with a timeline, instrument usage, and remove only after the old version is no longer used. For breaking changes, introduce a new version and keep the old one available for a defined period. Do not silently change semantics under the same name.
Test the old shape
Contract tests, replay tests, and golden fixtures should include historical payloads. A test that only uses the newest shape will miss a compatibility break. For databases, test a migration with old application code reading the migrated schema. For events, keep a corpus of old events and verify upcasting.
When to use it
Use backward compatibility when independent clients, rolling deployments, or long-lived data exist. The advantage is safe evolution and rollback; the drawback is the discipline of versioning and the cost of supporting old versions. Compared to a coordinated big-bang upgrade, compatibility is slower to remove old paths but avoids synchronized outages.