Deleting code is a design decision, not a cleanup chore. In the gap between API contracts, documentation, and developer experience, dead code is a live liability. It sits between the contract you think you have and the behavior your system actually shows. It misleads new contributors, bloats incident response, and quietly rewrites the meaning of your public surface. This article is about why removal should come before addition, and how to make deletion a first-class engineering practice.

I once watched a single unused branch in a payment API confuse an on-call engineer for 40 minutes during a production incident. The branch was unreachable. The documentation still referenced it. The contract said one thing, the code said another, and the developer experience said neither. That gap is where incidents breed.
The Sociotechnical Cost of Dead Code
Dead code is not just unused lines. It is an undocumented claim about system behavior. When a function exists, developers assume it can be called. When a parameter is accepted, integrators assume it does something. When a branch is present, operators assume it can execute. Each assumption is a small contract violation waiting to happen.
In one codebase I worked on, a legacy endpoint accepted a currency parameter that had been ignored for two years. The API documentation still listed it as required. A new integration sent currency=USD and received a 200 response with amounts in a different currency. The contract said one thing. The code said another. The documentation said a third. The gap between them was the bug.
Dead Code as a Documentation Failure
Documentation drifts because code drifts. When you delete a feature but leave the code, the documentation becomes a historical artifact rather than a current reference. The next person who reads the docs will assume the feature exists. The next person who reads the code will assume the docs are wrong. Both are correct, and both are wrong.
I once found a comment in a codebase that said, “This method is deprecated, do not use.” The method was still called in 14 places. The deprecation was a wish, not a fact. The code had not been deleted, so the deprecation had no teeth. The comment was a lie that everyone believed.
Why We Don’t Delete Code
Most teams avoid deletion for emotional reasons, not technical ones. The code represents effort. It represents a feature that someone fought for. It represents a future possibility. None of these are valid reasons to keep dead code in a production system.
I have heard every justification. “We might need it later.” “It’s not hurting anyone.” “It’s too risky to remove.” The first is a bet against version control. The second is a bet against your own incident response time. The third is a bet against your own test suite. All three are usually wrong.
The “We Might Need It Later” Fallacy
Version control exists. If you need the code later, it is in the history. Keeping it in the current tree does not make it more available. It makes it more dangerous. Every line of dead code is a line that can be misread, miscalled, or misdocumented.
In a protocol design review, I once saw a team keep an entire message type because a future feature might use it. The message type was never implemented. It sat in the schema for 18 months. When the future feature finally arrived, the message type was wrong for the new requirements. The team had to design a new one anyway. The old one had been dead weight the entire time.
Deletion as a Design Practice
Deletion should be a deliberate, scheduled activity, not a reactive cleanup. The best teams I have worked with treat deletion as part of the feature lifecycle. When a feature is deprecated, the code is removed in the same release. When an endpoint is retired, the handler is deleted, not commented out. When a parameter is ignored, it is removed from the contract.
This requires a different relationship with version control. You have to trust that the history is sufficient. You have to accept that the current tree is a working document, not a museum. You have to believe that a smaller codebase is a more honest codebase.
Deletion-Driven Development
I have started calling this deletion-driven development. Before you write new code, ask what you can delete. Before you add a parameter, ask if an existing one can be removed. Before you create a new endpoint, ask if an old one can be retired. The goal is not to write less code. The goal is to have less code that lies.
In one project, we reduced the API surface by 30% before adding a single new feature. The deletion took two weeks. The new feature took one. The deletion made the new feature easier to design, easier to document, and easier to test. The codebase was smaller, but the contract was clearer.

How to Delete Code Safely
Deletion is not reckless. It is a disciplined process with clear steps. The first step is to identify what is actually dead. The second is to verify that it is dead. The third is to remove it. The fourth is to update the documentation. The fifth is to communicate the change.
Step 1: Identify Dead Code
Dead code takes many forms. Unused functions. Unreachable branches. Ignored parameters. Deprecated endpoints. Commented-out blocks. Feature flags that are always off. Each form requires a different detection strategy.
Static analysis tools can find unused functions and unreachable branches. Code coverage tools can find code that is never executed in tests. API analytics can find endpoints that are never called. Log analysis can find parameters that are never used. The tools exist. The problem is that most teams do not run them regularly.
Step 2: Verify It Is Dead
Before you delete anything, verify that it is actually dead. Check the version history. Check the call sites. Check the documentation. Check the analytics. Check with the team. A function that looks dead may be called by a script you do not know about. An endpoint that looks unused may be called by a customer you have forgotten.
I once deleted a function that was called by a cron job that ran once a month. The function looked dead because it was not called in the main code path. The cron job was not in the repository. It was in a server configuration file. The deletion caused a production incident. The lesson: verify before you delete.
Step 3: Remove It
When you are confident that the code is dead, remove it. Do not comment it out. Do not leave a stub. Do not add a deprecation warning. Delete the code. The version history is your safety net. The test suite is your verification. The documentation update is your communication.
Commented-out code is the worst form of dead code. It is not executable, but it is still readable. It still misleads. It still creates the impression that the code might be re-enabled. It is a ghost that haunts the codebase.
Step 4: Update the Documentation
Deletion is not complete until the documentation is updated. The API contract must reflect the new reality. The developer guide must remove the deleted endpoint. The changelog must record the breaking change. The migration guide must explain what integrators should do instead.
I have seen teams delete code and forget the documentation. The result is a contract that promises features that no longer exist. The developer experience is worse than before the deletion. The code is cleaner, but the gap between contract and behavior is wider.
Step 5: Communicate the Change
Deletion is a breaking change. Even if the code was dead, someone may have been relying on it. Communicate the deletion before it happens. Give integrators time to adapt. Provide a migration path. Record the decision in the team’s decision log.
In one project, we deleted a deprecated endpoint without warning. A customer’s integration broke. The customer was angry. The deletion was technically correct, but the communication was wrong. The lesson: deletion is a social act, not just a technical one.
The Relationship Between Deletion and API Contracts
API contracts are promises. Dead code is a broken promise. When you delete code, you are updating the promise. When you leave dead code, you are leaving a promise that you cannot keep.
I have started thinking of API contracts as living documents. They change when the code changes. They change when the documentation changes. They change when the developer experience changes. Deletion is one of the ways they change. The question is whether the change is deliberate or accidental.
Contract-First Deletion
Contract-first deletion means updating the contract before you update the code. You announce the deprecation. You update the documentation. You give integrators time to migrate. Then you delete the code. The contract leads, the code follows.
This is the opposite of how most teams work. Most teams delete the code first and update the contract later. The result is a gap between contract and behavior. The gap is where incidents breed.
Deletion and Developer Experience
Developer experience is shaped by the gap between what the contract promises and what the code delivers. Dead code widens that gap. Deletion narrows it. A smaller codebase is easier to understand. A smaller API surface is easier to integrate with. A smaller set of parameters is easier to document.
I have watched new developers struggle with a codebase that contained three different implementations of the same feature. Two were dead. One was live. The new developer did not know which was which. They spent a week trying to understand the dead code before they realized it was dead. The deletion would have saved them that week.
The Onboarding Cost of Dead Code
Every line of dead code is a line that a new developer must read and evaluate. They cannot know it is dead until they have traced it. They cannot trace it until they have read it. They cannot read it until they have found it. The cost is paid by every new developer, every time.
In one codebase, I found a 200-line function that was never called. It had been dead for three years. Every new developer on the team had read it. Every new developer had wondered what it did. Every new developer had eventually realized it was dead. The function had cost the team hundreds of hours of confusion. The deletion took five minutes.
Deletion in Production Incidents
Production incidents are where dead code does the most damage. When an incident occurs, engineers are under pressure. They read code quickly. They make assumptions. They follow paths that look plausible. Dead code creates false paths.
I have been in incident war rooms where engineers traced a bug through a dead code path for 30 minutes before realizing the path was unreachable. The dead code had created a plausible explanation for the bug. The real bug was elsewhere. The dead code had cost the team 30 minutes of incident response time.
The False Path Problem
Dead code creates false paths. A false path is a code path that looks like it could execute but never does. When an incident occurs, engineers follow false paths because they look plausible. The false path leads them away from the real bug. The longer the false path, the longer the incident.
Deletion removes false paths. When the code is gone, the path is gone. The engineer cannot follow it. The engineer must look elsewhere. The deletion makes the incident response faster, not slower.
Deletion and Team Decision Records
Every deletion should be recorded in the team’s decision log. The record should explain what was deleted, why it was deleted, and what the alternatives were. The record should be linked from the changelog. The record should be searchable.
I have seen teams delete code without recording the decision. Six months later, someone asks why the code was deleted. No one remembers. The team spends an hour digging through the version history. The decision record would have answered the question in five minutes.
What to Record
A good deletion record includes the following: the name of the deleted code, the reason for deletion, the date of deletion, the person who made the decision, the people who approved it, the migration path for affected integrators, and the link to the version control commit. This is not bureaucracy. This is memory.
In one project, we kept a deletion log as a simple markdown file in the repository. Every deletion was recorded. The log became a valuable resource for new developers. They could see what had been deleted and why. They could avoid re-implementing features that had already been tried and removed.
The Emotional Resistance to Deletion
Deletion is emotionally hard. The code represents effort. The code represents a feature that someone fought for. The code represents a future possibility. Deleting it feels like admitting failure.
I have felt this resistance myself. I have written code that I was proud of. I have defended that code in design reviews. I have resisted deleting it. And I have been wrong. The code was dead. The deletion was right. The resistance was ego.
Deletion as Humility
Deletion is an act of humility. It is an admission that the code was not needed. It is an admission that the feature was not used. It is an admission that the future possibility did not materialize. This is not failure. This is learning.
The best engineers I have worked with are eager to delete their own code. They see deletion as a sign of progress, not a sign of failure. They understand that a smaller codebase is a better codebase. They understand that deletion is a design decision, not a cleanup chore.
Practical Deletion Checklist
Here is a practical checklist for deleting code safely:
- Identify the dead code using static analysis, coverage reports, and API analytics.
- Verify that the code is actually dead by checking call sites, documentation, and analytics.
- Record the deletion decision in the team’s decision log.
- Update the API contract and documentation before deleting the code.
- Communicate the deletion to affected integrators with a migration path.
- Delete the code in a single, reviewable commit.
- Run the test suite to verify that nothing breaks.
- Update the changelog with the breaking change.
- Monitor production for unexpected errors after the deletion.
This checklist is not exhaustive. It is a starting point. The key is to make deletion a deliberate, scheduled activity, not a reactive cleanup.
When Not to Delete Code
There are times when deletion is the wrong choice. If the code is behind a feature flag that is currently off but planned to be turned on, do not delete it. If the code is part of a public API that is under a deprecation period, do not delete it before the period ends. If the code is required for a compliance or regulatory reason, do not delete it.
The key is to distinguish between dead code and dormant code. Dead code will never be used. Dormant code is temporarily unused. Dead code should be deleted. Dormant code should be documented and scheduled for review.
Dead Code vs. Dormant Code
Dead code is code that has no future. It is not called. It is not planned to be called. It is not required for compliance. It is a ghost. Dormant code is code that is temporarily unused. It is behind a feature flag. It is waiting for a migration. It is required for a future release. Dormant code should be documented, not deleted.
The distinction matters. Deleting dormant code can cause incidents. Keeping dead code can cause incidents. The skill is in telling the difference.
Deletion as a Recurring Practice
Deletion should be a recurring practice, not a one-time event. Schedule a deletion review every quarter. Run the static analysis tools. Review the API analytics. Identify the dead code. Delete it. Record the decisions. Update the documentation.
I have seen teams do this as a quarterly ritual. The ritual takes a day. The result is a smaller, clearer codebase. The team feels lighter. The contract is more honest. The developer experience is better.
The Quarterly Deletion Review
A quarterly deletion review is a scheduled meeting where the team reviews the codebase for dead code. The meeting has a clear agenda: identify dead code, verify it is dead, decide what to delete, and assign owners. The meeting is not a debate about whether deletion is good. The meeting is a working session to do the deletion.
In one team, the quarterly deletion review became the most popular meeting on the calendar. Engineers looked forward to it. They brought lists of dead code they had found. They competed to see who could delete the most. The meeting was fun, and the codebase got smaller every quarter.

The Future of Deletion
Deletion is not a new idea. It is an old idea that has been forgotten. The best engineers have always deleted code. The best teams have always kept their codebases small. The best contracts have always been honest. The gap between contract and behavior has always been the source of incidents.
The future of deletion is not a new tool or a new process. It is a new attitude. It is the attitude that deletion is a design decision, not a cleanup chore. It is the attitude that a smaller codebase is a better codebase. It is the attitude that the contract should match the code, and the code should match the documentation.
This article is part of a series on the sociotechnical gap between API contracts, documentation, and developer experience. The next article will examine how to write deprecation policies that integrators actually read. If you have a deletion story from your own codebase, I would like to hear it.
FAQ
What is the difference between dead code and dormant code?
Dead code is code that will never be used again. It is not called, not planned to be called, and not required for compliance. Dormant code is temporarily unused. It may be behind a feature flag, waiting for a migration, or required for a future release. Dead code should be deleted. Dormant code should be documented and scheduled for review.
How do I know if code is actually dead before deleting it?
Verify using multiple sources. Check static analysis tools for unused functions and unreachable branches. Check code coverage reports for code that is never executed in tests. Check API analytics for endpoints that are never called. Check log analysis for parameters that are never used. Check with the team for scripts or cron jobs that may call the code outside the main repository. Only delete after all sources agree.
Why is commented-out code worse than dead code?
Commented-out code is not executable, but it is still readable. It still misleads developers into thinking the code might be re-enabled. It still creates false paths during incident response. It still widens the gap between the contract and the behavior. Dead code at least has the honesty of being executable. Commented-out code is a ghost that haunts the codebase without even being able to run.
How often should a team schedule deletion reviews?
Quarterly is a good starting point. A quarterly deletion review takes about a day and can identify a significant amount of dead code. Teams with large codebases or frequent feature churn may benefit from monthly reviews. The key is to make deletion a recurring practice, not a one-time event.
What should be recorded in a deletion decision log?
Record the name of the deleted code, the reason for deletion, the date of deletion, the person who made the decision, the people who approved it, the migration path for affected integrators, and the link to the version control commit. This is not bureaucracy. This is memory. It prevents future teams from re-implementing features that have already been tried and removed.