← Back to System Design Fundamentals

API Versioning

System Design FundamentalsMidsystem-design

The Question

What are the common approaches to API versioning?

What a Strong Answer Covers

  • At least two strategies.

Senior-Level Answer

API versioning lets you evolve a public API without breaking existing clients. The four main approaches each make different trade-offs between discoverability, client coupling, and REST purity.

**URI path versioning** (`/v1/users`, `/v2/users`) is the most common approach. The version is explicit, visible in logs and browser address bars, easy to route at the load balancer level, and simple to document. The downside is that it violates REST's resource identity principle — `/v1/users/123` and `/v2/users/123` represent the same resource. It also encourages wholesale version duplication rather than incremental evolution.

**Query parameter versioning** (`/users?version=2`) is easy to implement and keeps the base URL stable, but query params are semantically for filtering, not versioning. Caching behavior is complicated — many CDNs and proxies treat query strings differently, and some strip or ignore them.

**Header versioning** (`Accept-Version: 2` or a custom `X-API-Version` header) keeps URLs clean and aligns with HTTP's content negotiation model. It is harder to test in a browser (can't just type the URL) and less discoverable. This is the approach used by Stripe (`Stripe-Version: 2023-10-16`) and GitHub.

**Content negotiation / Accept header** (`Accept: application/vnd.myapi.v2+json`) is the most RESTful approach — the same URL returns different representations based on the Accept header. Used by GitHub's API. It is the least discoverable and hardest for API consumers unfamiliar with HTTP media types.

In practice, URI versioning is the pragmatic winner for public APIs because it is immediately understandable and easily tested with curl. Header versioning is preferred by sophisticated API platforms (Stripe) because it ties versions to timestamps (breaking changes only on specific dates) rather than requiring parallel codebases.

A critical strategy regardless of versioning scheme is **additive-only changes without version bumps**: adding optional fields, new endpoints, and new enum values are non-breaking. Breaking changes — removing fields, changing types, altering semantics — should require a version bump. The goal is to minimize the number of major versions that must be maintained in parallel.

Version sunset policy (deprecation warnings via `Deprecation` and `Sunset` headers) and a clear end-of-life timeline are as important as the versioning scheme itself.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Names at least three versioning strategies and correctly describes URI versioning trade-offs. May not distinguish breaking vs. non-breaking changes or mention deprecation policy.

3/3 — Strong Answer

Covers all four strategies with precise trade-offs, can identify which real-world APIs use each approach, distinguishes breaking from non-breaking changes, mentions Deprecation/Sunset headers, and gives a recommendation with reasoning.

Common Mistakes

  • Treating API versioning as purely a technical question without addressing the client communication and deprecation lifecycle.
  • Not knowing what constitutes a breaking vs. non-breaking change — adding optional fields is non-breaking; removing or renaming fields is breaking.
  • Recommending query-parameter versioning without acknowledging the caching complications.
  • Conflating API versioning with database schema versioning.

Follow-Up Questions

  • How does Stripe handle API versioning and why is their approach considered best-in-class? — Stripe uses dated version strings (YYYY-MM-DD) in a header; clients pin to the version that worked when they integrated. Stripe maintains a changelog of breaking changes per version date.
  • What is the Sunset HTTP header and how would you use it? — RFC 8594 — Sunset: <HTTP-date> tells clients when the endpoint will stop working. Paired with Link: <docs-url>; rel=deprecation to guide migration.
  • How would you handle a versioning strategy for an internal API that only your own team consumes? — Internal APIs can use more aggressive evolution with coordinated deploys rather than long-lived parallel versions. Contract testing (Pact) is often more practical than URL versioning.
  • What is the difference between versioning an API and versioning a resource representation? — Resource-level versioning (content negotiation) versions the shape of the response independently per resource type. API-level versioning applies a version to the entire API surface.

Related Questions

  • Redis Caching Patterns
  • Vertical vs Horizontal Scaling
  • SLOs vs SLAs
  • Availability — Five 9s
  • Latency vs Throughput

Can You Explain This Cold?

Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.

Get Graded — Free Assessment
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout