The Bit Shift

Break the Glass

Judgment is knowing when to break the glass and when to just clean it.


Some breaking changes are necessary violence. Others are just violence.

The entire industry coordinated to deprecate TLS 1.0 (opens in a new tab) because the protocol had fundamental security flaws that made it a liability to keep breathing. That is an existential breaking change. The cost of not breaking it was higher than the cost of migration. You break backward compatibility when the alternative is systemic failure.

Most breaking changes don’t meet that bar. Renaming a field for consistency is not an emergency, and neither is reorganizing your JSON response for “cleanliness” or bumping a major version because you finally fixed that three-year-old typo in the documentation. That’s pushing work onto your users because you found the old naming convention embarrassing.

The Python 2 to 3 migration is the canonical example of a justified change executed poorly. The improvements to string handling and Unicode support were real. The decade-long fragmentation of the ecosystem was also real. Libraries stopped getting updates because maintainers had to choose between supporting two incompatible versions or abandoning users. The industry paid a steep collective price for a transition that could have been designed with more grace.

Chrome’s Manifest V3 (opens in a new tab) had legitimate security goals but forced extension developers into a rewrite with an aggressive timeline. Tailwind v4 delivered 100x faster builds but left developers staring at breaking changes in their utility classes. Both were defensible, and both hurt.

So before you break anything, ask whether you’re patching a hole or redecorating.

If you’re redecorating, the migration cost belongs to you, not your users.

If it’s redecorating, use the expand and contract pattern. Add the new thing, deprecate slowly, and let your users migrate on their timeline, not yours.

Judgment is knowing when to break the glass and when to just clean it.