AW Dev Rethought

✂️ Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away - Antoine de Saint-Exupéry

Architecture Realities: The Cost of Ignoring Backward Compatibility


Introduction:

Backward compatibility is one of those engineering concerns that feels optional until it is not. When a system serves a small number of consumers, all maintained by the same team, breaking changes are manageable — update the consumers alongside the API, deploy everything together, and move on. The cost of ignoring backward compatibility at this scale is low enough that it rarely registers as a problem.

As systems grow, the cost changes fundamentally. More consumers mean more teams to coordinate with, more deployment timelines to align, and more opportunity for a breaking change to reach production before every consumer has been updated. External APIs that serve third-party developers introduce consumers that cannot be updated at all — they update on their own schedule, or never. Mobile applications that embed API assumptions in shipped binaries create consumers that persist in production for months after a breaking change is deployed.

The cost of ignoring backward compatibility is not paid when the decision is made. It is paid gradually, in failed deployments, in production incidents, and in the engineering time spent coordinating changes that should have been designed to be non-breaking from the beginning.


Breaking Changes Compound Across Consumers:

A breaking change to an API that has one consumer requires coordinating one update. The same breaking change to an API with fifty consumers requires coordinating fifty updates — across teams with different priorities, different deployment schedules, and different levels of urgency about the change being made.

In practice, this coordination rarely happens perfectly. Some consumers update quickly. Others deprioritise the migration. A few are maintained by teams that no longer exist or that have moved on to other systems. The API provider is left maintaining both the old and the new versions simultaneously — because removing the old version would break the consumers that have not yet migrated — while the consumers that have already migrated are now blocked from receiving further improvements until the migration is complete.

This is the backward compatibility debt cycle. Breaking changes that could have been avoided create migration burdens that delay subsequent changes, which creates pressure to make further breaking changes rather than designing around the constraints, which creates more migration burdens.


API Versioning Solves Part of the Problem:

API versioning — maintaining multiple versions of an API simultaneously — is the standard response to backward compatibility requirements. Consumers that depend on v1 continue using v1. New consumers adopt v2. The provider maintains both until v1 usage has declined enough to justify deprecation.

Versioning solves the immediate problem of breaking consumers but introduces its own costs. Every version that is maintained is a version that must be tested, monitored, and operated. Bug fixes that affect shared logic must be applied to every supported version. Security vulnerabilities must be patched across all versions simultaneously. The operational burden of maintaining multiple API versions grows with the number of versions and the length of time they are supported.

Versioning is a necessary tool but not a substitute for designing APIs that do not require frequent breaking changes. The teams that manage API versioning most effectively are the ones that version infrequently — because they design their APIs to be extensible in ways that accommodate new requirements without breaking existing consumers.


Additive Changes Are Almost Always Possible:

The most common reason engineers make breaking changes is that they believe they have no choice — the existing interface cannot accommodate the new requirement without modification. In most cases this belief is wrong. Most requirements that seem to demand breaking changes can be accommodated through additive changes — changes that add new fields, new endpoints, or new behaviours without modifying or removing existing ones.

Adding a new optional field to an API response is additive. Consumers that do not need the field ignore it. Consumers that need it can adopt it at their own pace. Adding a new endpoint that provides enhanced functionality alongside the existing endpoint is additive. Consumers migrate when ready rather than when forced.

The discipline of designing for additive change requires thinking about extensibility at API design time rather than at change time. APIs that are designed to be extended — with clear conventions for adding fields, with explicit versioning of breaking versus non-breaking changes, and with deprecation processes that give consumers time to migrate — accumulate backward compatibility debt more slowly than APIs that are designed purely for current requirements.


Client Diversity Makes Breaking Changes Expensive:

The cost of a breaking change scales with the diversity of consumers. A backend service whose only consumer is another backend service in the same organisation has a low backward compatibility cost — both systems can be updated and deployed in coordination. A public API whose consumers include mobile applications, third-party integrations, partner systems, and internal services has a backward compatibility cost that is orders of magnitude higher.

Mobile applications are particularly unforgiving. A breaking change deployed to a mobile API affects every version of the mobile application that is currently installed on user devices. Users who have not updated their application — which may be a significant fraction of the user base — will encounter failures until they update. The engineering team cannot force the update, cannot predict when it will occur, and must maintain backward compatibility for the old application version until its usage has declined to an acceptable level.

Designing APIs for client diversity means assuming from the beginning that consumers will be heterogeneous, slow to update, and outside the direct control of the API provider. This assumption produces more conservative API design decisions — fewer breaking changes, longer deprecation timelines, and more investment in additive extensibility mechanisms.


Deprecation Without Enforcement Creates Zombie APIs:

Deprecation is the standard mechanism for signalling that an API version or field will eventually be removed. In practice, deprecation without enforcement rarely leads to migration. Consumers that receive a deprecation notice deprioritise migration until the deprecated endpoint is actually removed — because migration has a cost and deprecation notices have no immediate consequence.

This produces zombie APIs — deprecated endpoints that continue receiving significant traffic long after their deprecation was announced, because the announcement created no incentive to migrate. The API provider cannot remove the endpoint without breaking live traffic. The consumers have no urgency to migrate because the endpoint still works.

Effective deprecation requires enforcement mechanisms that create genuine incentives to migrate — declining reliability guarantees for deprecated endpoints, explicit sunset dates with consequences, or tooling that surfaces deprecated API usage to engineering teams in a way that creates visibility and accountability. Deprecation that relies on consumers choosing to migrate voluntarily, on their own timeline, without any consequence for delay, rarely results in timely migration.


Conclusion:

The cost of ignoring backward compatibility is not the cost of a single breaking change. It is the accumulated cost of every migration that breaking change requires, every version that must be maintained simultaneously, every incident caused by a consumer that was not updated in time, and every subsequent change that is delayed because the migration burden from the previous breaking change has not yet been resolved.

Engineering teams that treat backward compatibility as a first-class design concern from the beginning — designing for additive extensibility, maintaining clear deprecation processes with real enforcement, and evaluating every API change for its impact on existing consumers before making it — pay a small ongoing cost that prevents a much larger accumulated cost from developing over time. The alternative is discovering that cost only after it has become significant enough to slow down every change the system needs to make.


If this article helped you, you can support my work on AW Dev Rethought.


Rethought Relay:
Link copied!

Enjoyed this post?

Stay in the loop

New posts + weekly digest, straight to your inbox.

or

Create a free account

  • Save posts to your vault
  • Like posts & build history
  • New-post alerts

Comments

Add Your Comment

Comment Added!