Versioning & Backward Compatibility
Prefer additive change with tolerant readers so you rarely version, use visible /v1 path versioning for true breaks, and retire versions with deprecate-warn-remove.
The best versioning strategy is to rarely need it
Versioning exists to let you change an API without breaking the integrations already depending on it. The core insight most engineers miss: the best versioning strategy is to need it as rarely as possible. Most changes should be additive and never trigger a version bump at all.
What breaks a client and what does not
Adding an optional field, adding an endpoint, adding an enum value that clients already ignore when unknown: non-breaking. Removing a field, renaming a field, changing a type, tightening validation, changing default behavior: breaking. If you design for additive change and your clients are tolerant readers (they ignore unknown fields and do not assume the response is exhaustive), the large majority of your evolution costs zero version bumps.
Your public API is at /v1 and your clients are tolerant readers. Sort each proposed change.
When you genuinely must break
- URL-path versioning (
/v1/orders,/v2/orders). Visible, trivial to route, trivial to test withcurl, and easy for developers to reason about. This is the pragmatic default for public REST APIs (Stripe, Twilio, GitHub all expose a visible version). - Header or media-type versioning (
Accept: application/vnd.acme.v2+json). Purer from a REST standpoint because the resource URL is stable, but it is invisible in a browser address bar, harder to test casually, and easy for a proxy to strip or ignore.
Per-paradigm nuance: GraphQL avoids URL versions entirely and evolves field by field, marking old
fields @deprecated with a reason and adding new ones. gRPC follows Protobuf's field-number rules:
add new fields with new tags, never renumber, mark removed tags reserved, so old and new binaries
interoperate.
Compatibility runs two directions. Backward compatibility: a new server can still serve old clients. Forward compatibility: an old client can tolerate data from a new server (this is exactly what the tolerant-reader pattern buys you). You want both, because in a distributed deploy the two sides are never upgraded at the same instant.
Retiring a version is a sequenced migration
Deprecate (announce, document the replacement), warn (return Deprecation and Sunset headers,
log usage, email the top callers), then remove only after telemetry shows traffic has drained. A hard
cutover with no warning is how you generate an angry customer incident.
Interview nuance: the strongest signal is saying "I would design so most changes are additive and never bump the version, and only cut /v2 for a true break," then describing the deprecate-warn-remove sequence. Jumping straight to "put v1 in the URL" misses that versioning is a last resort.
Recap: prefer additive change with tolerant readers so you rarely version, use visible /v1 path versioning for true public breaks, and retire old versions with a deprecate then warn then remove sequence.
Apply
Your turn
The task this lesson builds to.
Design a versioning strategy that lets you ship a breaking change to a public API without breaking existing integrations.
Think about
- URL-path vs header/media-type versioning, and which is the visible default?
- How do additive changes and tolerant readers avoid version bumps?
- How do you sequence a migration: deprecate, warn, remove?
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Design a versioning model that lets you evolve a payments API for a decade while every integration written on day one still works (as Stripe has done since 2011, shipping changes constantly without ever breaking its API). Explain the mechanism and how new behavior reaches new callers without a /v2.
Think about
- What does pinning a version per account (rather than per URL) change about migration pressure?
- How can one canonical implementation serve a decade of historical response shapes?
- Why do coarse /v1-/v2 URL versions fail at a ten-year horizon?