Deprecate Like You Mean It
If the contract is what people actually depend on and versioning is how you communicate changes to it, deprecation is the tool for retiring part of that contract without breaking it out from under anyone. In theory it’s simple: mark the thing as deprecated, wait, remove it. In practice, deprecation notices are some of the most widely ignored text in software — a comment nobody reads, a log line nobody watches, a doc page nobody visits before they’re already stuck. The tool exists, gets used constantly, and very often fails at the one job it has, which is getting people to stop depending on something before you take it away.
The core problem is that a deprecation notice asks for future effort in exchange for no present benefit. The deprecated thing still works today, exactly as it did yesterday, so there’s no forcing function — the code that would need to change keeps not needing to change, right up until the day it’s removed and suddenly does. This is a predictable incentive problem, not a discipline failure on the part of callers: humans and teams reliably defer costless-today, costly-later work, and a comment in a changelog is a weak counterweight to that. If you want a deprecation to actually result in migration, the notice itself has to do more than exist — it has to become a nuisance proportional to the size of the ask, or it has to include something that meaningfully lowers the cost of the migration, ideally both.
This is where the earlier pieces converge. The deprecation only means anything if you know what you’re deprecating — which requires knowing what’s actually depended upon, not just what’s officially exposed, the same gap that runs through the entire contract question. It should be communicated with a version bump when it changes what happens on removal, which is the versioning discipline applied honestly. And when the removal finally happens, it needs the same care given to any breaking change: proportional notice, a clear path, done once. Deprecation isn’t a separate concern from the rest of this thread — it’s contract management with a clock attached, and it inherits every difficulty the rest of the thread already covered.
A few things make a deprecation notice actually work rather than just exist. Make the cost of ignoring it visible before removal, not just at removal — a runtime warning that actually surfaces somewhere someone will see it, not a debug-level log line nobody enables. Give a genuine migration path, not just an announcement that something is going away — the replacement should exist and be easy to move to before you ask anyone to move. Set and hold a real timeline, because a deprecation with no removal date is not a deprecation, it’s a suggestion, and everyone treats it as one; but a timeline you don’t hold to teaches people that your deprecations are all suggestions, which poisons the ones that follow. And find out who’s actually still using the old path before removing it — silence is not the same as nobody being affected, it’s the same silence the pieces on defaults already warned about.
None of this is really about how to write a deprecation notice. It’s about recognizing that deprecation is a commitment with two parties, not a broadcast with one — you’re asking people to do work on your timeline, and the notice only succeeds if it makes that work visible, cheap, and eventually unavoidable. Treated that way, deprecation closes the loop this whole thread has been describing: know what people actually rely on, communicate change honestly through the version, and when something must go, make its going a managed event rather than a surprise. That’s the discipline an interface asks of you in exchange for the freedom to ever change it again.