Backward Compatibility

Concept

The property of letting code written against an older version keep running untouched, even while new capability is added on top of it.

Why it exists

An application programming interface (API) is an agreed counter where one program hands work to another. Once you open that counter, other people write code against it, and the moment their code ships it leaves your hands. It runs on another company's servers, inside phones that have already been sold, and long after the engineer who wrote it has left.

The problem is what happens when you reshape the counter while all of that is still running. On a Friday evening you drop a field nobody seems to use and deploy. On Monday morning three integration partners call. They haven't touched a line of their own code, their payments have stopped, and the outage started at the minute you deployed. A change like that is a breaking change.

Partners only have to live through this once or twice before the math changes for them. Nobody puts their company's payment flow on a counter that can be reshaped without warning. And when people stop building on the counter, the reason for opening it disappears.

So backward compatibility isn't a feature you build. It's a property you commit to holding. Callers using the old shape keep working, and new capability gets layered on top for whoever wants it.

How it works

You hold it by only adding, never taking away. Five rules cover most of it.

  1. Only add fields. Putting a new nickname into a response is safe, because old code walks past a key it doesn't recognize. Removing name stops every caller that was reading it.
  2. Don't rename. Changing owner to ownerId looks like tidying up, but to the caller it is one field vanishing and an unfamiliar one appearing. Add the new name and keep emitting the old one alongside it for a while.
  3. Don't make new inputs required. If a request starts accepting a currency field, give it a default and treat a request without it as dollars. Make it required and a call that worked yesterday gets rejected today.
  4. Don't change the meaning of values you already emit. If a status field switches from ok to success, the key is unchanged but every condition comparing against ok quietly stops matching. Agreed numeric values like status codes work the same way.
  5. Write the receiving side to ignore fields it doesn't know. That is what makes it safe for the other side to add. It's part of why formats like JSON, which look values up by key name, became the default.

The structure is the mail forwarding you set up when you move house. Moving is your business, but letters sent to the old address still have to arrive for a while. Telling everyone who knows the old address before that window closes is deprecation.

Where it shows up

The obligation appears anywhere you have shipped something you can no longer reach in and fix. Four places are typical.

  1. Public APIs. It makes little difference whether the counter is REST, which addresses each resource by its own URL, or GraphQL, where the caller lists exactly the fields it wants. Webhooks are harder still, because there you are the one pushing to someone else's address, so changed contents leave the receiver silently working from wrong values.
  2. Libraries other people depend on. Semantic versioning puts the property into the version number itself. A move from 2.4.1 to 2.5.0 says capability was added; a move to 3.0.0 says compatibility broke and there is work to do before upgrading.
  3. Stored data. Dropping a column from a database table is dangerous for a short window rather than a long one. While a deploy is rolling, new code and old code read the same table for a few minutes, so teams separate the deploy that adds a column from the deploy that removes one by weeks.
  4. Apps already installed. This is the painful one. You can change a server today, but an app on someone's phone updates only when they open it. If old versions are 12 percent of installs, that 12 percent keeps calling your server with the old shape.

None of this lasts forever. Look at how many calls actually read the old field per day over the last 30 days, and take it out when that number approaches zero.

How it sounds at work

"Is this change backward compatible? If not, we need to cut a separate version."

Check whether anything calling the current shape breaks, before deciding anything else.

"Let's add a new field instead of removing that one. We can drop the old one in six months."

A proposal to emit both fields at once so callers get time to move.

"Old app versions are still 12 percent, so we can't remove it this quarter."

We could delete it technically, but that 12 percent stops working today, so it waits.

At Pagedeck, Theo once proposed renaming the owner field in the document list response to ownerId, on the grounds that the value was an identifier rather than a person's name. Mia counted who was reading that field and found four enterprise customers wired into it.

They landed on emitting both. ownerId went in, owner stayed exactly as it was, and the documentation noted that it was scheduled for removal. Four emails went out and the deadline was set six months later. On the deadline, calls still reading the old field were down to two a day, and someone tracked down whose they were before it came out.

What people get wrong

The most common mistake is hearing "keep it backward compatible" as "don't change anything." A request not to break callers sounds like a request not to touch the thing at all. What is actually off the table is removing and renaming, while adding stays wide open. Teams that keep the misreading go one of two ways. They give up on improving the interface and leave the shape untouched for a decade, or they change it quietly, tell nobody, and wait for the incident.

The other one is assuming the field names in the response are the whole contract. The documented list of fields is what you wrote down, so it feels like the boundary of what you promised. But integrators lean on things nobody wrote down: that a list always came back newest first, that a particular error number came back on failure, that a page never held more than 100 items. That is how a deploy switching sort order to alphabetical wrecks somebody's screen without touching a single field name. The contract is defined by what the other side actually depends on, which you find out by measuring rather than by reasoning about it.

Easily confused with

TermWhat it refers toHow it differs
API versioningRunning an old and a new shape of the interface side by side when the specification changesThe tool you reach for when compatibility can't be held. Compatibility is the property you try to preserve and versioning is what you do once you couldn't, so cutting new versions often is a sign you are losing the first one

Where to go next

  • API

    start with the counter itself, since that is what you are promising not to break

  • REST

    the concrete style where compatibility questions come up most often

Last updated 2026-09-16