Writing code feels productive. Deleting code feels dangerous. This asymmetry is one of the most persistent biases in software engineering, and it leads to codebases that only ever grow.

The Accumulation Problem

Every codebase I’ve worked on has dead code in it. Functions nobody calls. Feature flags that will never be toggled. Entire modules that were superseded two years ago but never removed because someone thought they might be needed again.

The code sits there, inert. It doesn’t crash anything. It doesn’t slow anything down – at least, not directly. So it stays. And it accumulates. And slowly, the codebase becomes a geological record of every decision anyone ever made, including the ones that were reversed.

The cost of this accumulation is real, even if it’s hard to measure. Every developer who reads through the code to understand the system is navigating around dead branches. Every search for a function name returns results from abandoned modules. Every refactor has to consider whether this untouched code still matters. The answer is almost always no, but figuring that out takes time every single time.

Why Deletion Is Hard

Adding code is low-risk in a specific way: if the new code is wrong, you can revert it. You know what “before” looked like because it was the system five minutes ago.

Deletion inverts this. If you delete the wrong thing, you’ve removed something that might be hard to reconstruct. Yes, version control exists. But finding the right commit, extracting the right pieces, re-integrating them into a codebase that has moved on – that’s not a trivial recovery.

There’s also a psychological component. Code represents work. Someone wrote it, debugged it, tested it, reviewed it. Deleting it feels like erasing that effort. Even when you know intellectually that the value of the code was in the problem it solved at the time, not in its continued existence, it still feels wrong to highlight a hundred lines and hit delete.

And then there’s the uncertainty. “What if we need this later?” is the most common argument against deletion, and it’s nearly impossible to refute. You can’t prove a negative. You can’t demonstrate that a piece of code will never be needed again. So the safe choice, the one nobody gets blamed for, is to leave it in.

The Case for Aggressive Deletion

Dead code has a carrying cost that compounds over time. It confuses new contributors. It inflates search results. It creates the illusion of complexity. It makes the system harder to reason about, harder to test, and harder to change.

Every line of code you keep is a line of code you’re maintaining, whether you know it or not. It interacts with your linter, your type checker, your build system, your mental model. Removing it isn’t just cleanup – it’s reducing the surface area of the system.

The best codebases I’ve worked on had a culture of deletion. Not reckless deletion – informed, deliberate deletion. When a feature was retired, the code was removed in the same sprint. When a refactor replaced an old approach, the old approach was deleted, not commented out. When a utility function lost its last caller, it was gone.

Practical Deletion

The mechanics matter. You can’t just delete things blindly. But you can build confidence:

Search for callers. Before deleting a function, grep the entire codebase. Check for dynamic invocations, reflection, string-based lookups. If nothing references it, it’s a candidate.

Check your tests. If a function has no tests and no callers, it’s almost certainly dead. If it has tests but no callers, the tests are testing dead code – delete both.

Use feature flags with expiration dates. When you add a flag, decide upfront when it will be removed. Put it in the backlog. Make deletion part of the feature lifecycle, not an afterthought.

Delete in isolated commits. If something goes wrong, you want to revert the deletion without reverting unrelated changes. A clean commit that only removes code is easy to reason about and easy to undo.

Version Control Is Your Safety Net

The strongest argument against the fear of deletion: nothing is truly gone. Every line you remove still exists in the commit history. If you need it back – and in my experience, you almost never do – it’s there.

The code isn’t destroyed. It’s archived. The difference is that archived code doesn’t slow down everyone who’s trying to work on the living codebase.

Delete more. The codebase will thank you.