I used to keep code for the product we hoped to have. A configuration option for a customer who might one day ask for it. An abstraction for a second payment provider that never arrived. A feature flag for an experiment that ended in 2021 and was never quite switched off.
Now I try to keep code for the product we actually have, which is smaller and a good deal less interesting. The codebase weighs noticeably less than it did, and the team moves faster in it.
Code is not an asset
It is tempting to think of a codebase as something you own, like stock in a warehouse. Every line cost money to write, so deleting one feels like throwing money away.
That is the wrong way round. Code is closer to a lease. Every line has to be read by the next person who opens the file, kept working through every upgrade, and considered whenever something nearby changes. Most of the cost of a line is paid after it is written, for as long as it exists.
The costs are easy to list once you look for them: slower builds, longer test runs, more places for a security advisory to land, more screens for a new starter to understand before they can be useful, and more chances that two paths through the same feature disagree with each other.
How we delete, in practice
We tried clean-up sprints and they never worked, because clean-up is the first thing cut when a deadline slips. What worked was making deletion a small, frequent habit:
- Every feature flag gets a removal date and a ticket to remove it on the day it is created.
- Anything nobody can explain in a sentence gets a comment asking who owns it. If nobody answers within a fortnight, it goes.
- Pull requests that only delete code are reviewed first. It is the quickest review anyone does all day, and it tells the team the work is valued.
- Every quarter we look at the ten largest files and ask what each one would look like if it only did the thing it is named after.
What we do not delete
The habit needs a boundary or it becomes its own kind of recklessness. We do not delete anything we are required to keep: audit trails, financial records, the migrations that built the current schema, or the ugly compatibility shim that keeps two years of old mobile clients working. We do not delete tests because they are slow; we fix them or quarantine them with a date attached.
And we do not delete on a Friday, for the same reason we do not deploy on one.
The fear
The reason people do not delete things is fear, and the fear is reasonable. Somewhere, someone might depend on that endpoint. The honest answer is usually to find out: add logging, wait a month, look. Most of the time the logs stay silent. Occasionally they do not, and you learn something about your own system that nobody knew.
When we did break something by deleting it, which happened twice in a year, the fix was quick. The code was still in version control and the person who removed it knew exactly what they had done. That is a very different kind of incident from the ones caused by code nobody understands.
It helps to name the scariest version out loud. In our case it was an export endpoint with no traffic in the logs. It turned out one customer called it once a quarter, from a spreadsheet, on the last working day of the quarter. Logging for a month would not have caught it. Asking the support team, which took four minutes, did.
What it did to the build
The effects took about two quarters to show up, and then they showed up everywhere. The test suite went from nineteen minutes to nine, which changed how often people ran it. The bundle the browser downloads lost about a fifth of its weight. Two long-postponed dependency upgrades became possible in an afternoon, because the code that made them awkward was the code we had removed.
The change I value most is harder to measure. New engineers stopped asking “which of these three functions is the real one”, because there was only one of each.
How to start when the team is nervous
Start with something nobody will miss and make it visible. Pick the dead feature flags, delete them in one pull request, put the line count in the team channel, and see who flinches. Then do it again a fortnight later. After a few rounds, people bring you their own candidates, and the conversation changes from “can we remove this” to “why are we still carrying this”.
Who is allowed to delete
We were asked, fairly, whether a habit like this is safe in a team where people come and go. The rule we settled on is that anyone may delete anything they can explain, and nobody deletes anything they cannot. In practice that means a new engineer removes dead flags in week two and does not touch billing until month six, and it needs no policy document to enforce.
The other half is that deletions go through the same review as any other change. A removal is a change in behaviour even when the behaviour was nothing, and the person who wrote the code originally, if they are still here, gets to read it first. Twice that has turned into a five-minute conversation that saved us from a genuinely bad idea.
Rewrites
People sometimes ask whether this is a way of putting off a rewrite. I think it is the opposite. A rewrite is usually what happens when a team has been afraid to delete things for years, and the only way left to shed the weight is to start again. Small, regular deletions are how you avoid ever needing one.
The best pull request I reviewed last year removed four thousand lines and added eleven. Nobody outside the team noticed a thing, which is exactly the point.

Leave a Reply