concept

Additive-Only Evolution

also called Non-Breaking Change Discipline, Tolerant Reader Contract

Establishing the compatibility contract before the first release - unknown fields ignored, unknown enum values defaulted - so that most future change requires no version at all.

razorpayversioningcompatibilitydeprecationclerk

Versioning is expensive at scale: every live version is a translation to maintain, a behaviour to test, and a population to eventually migrate. The cheapest version is the one you never had to create, and most changes do not need one if the contract permits addition.

The contract: unknown fields are ignored, unknown enum values fall to a documented default, field order is not meaningful, and additional fields may appear at any time.

Why it matters

It has to exist before the first version ships. Integrators who never received that instruction write parsers that reject unknown fields and switch statements that throw on unknown enum values, and once thousands of them exist, adding a field becomes a breaking change — which is how APIs end up with a version for every quarter.

Implementation patterns

  • State the tolerant-reader contract in documentation and enforce it in every official SDK, since most integrators use the SDK and its behaviour becomes the de facto contract.
  • Pin versions per account, date-based or explicit, so an integrator's behaviour is fixed until they choose to move. This decouples your release cadence from theirs entirely and is what makes many versions survivable.
  • Keep versions in a translation layer, not in the domain. The core speaks one model; the boundary maps old versions onto it. The version count is then a cost paid in one place rather than a set of conditionals spread through the business logic.
  • Instrument usage per version and per field, so deprecation is evidence-driven and "which customers read this field" is answerable in minutes rather than being unknowable.
  • Publish a deprecation policy with a real notice period, and use in-band communication: deprecation headers on every response, warnings in the developer dashboard, and — as the final step — brief scheduled brownouts that make the deadline real while it is still reversible.

Industry example

Payment platforms such as Razorpay and identity platforms such as Clerk and Auth0 carry thousands of integrations across many SDK generations, where a breaking change is effectively impossible: integrators cannot re-certify quickly, and in regulated industries the change may require their own compliance review.

Deprecation in these environments fails on communication rather than engineering — the original developer has left, the account contact sits in finance, and the notification email reaches an unmonitored address. In-band signals are the only ones that reach the person who can act.

Failure scenarios

  • No tolerant-reader contract from the start, making addition a breaking change.
  • Version conditionals spread through the domain, so every version raises the cost of every future change.
  • Deprecation without per-version usage data, forcing either an indefinite maintenance commitment or a removal that breaks unknown customers.
  • Notice sent only by email, reaching nobody who can act.
  • Semantic change shipped as non-breaking — a field whose meaning changed while its shape did not — which is the most dangerous category because no automated check catches it.

Trade-offs

Additive-only evolution means the API accumulates fields that are no longer used and shapes that reflect old thinking. It gets uglier over time, and that ugliness is the price of not breaking people.

Periodic major versions are the release valve, and they are worth doing rarely and thoroughly rather than often. The cost of a major version is measured in customer engineering effort, not yours, which is the calculation teams consistently get wrong.

Interview question

"You need to change a field's meaning. It is not a shape change, so nothing will break at parse time. What do you do, and how do you find out who depends on the current meaning?"