The simple version

Every line of code you write is something that can break. The fewer lines you have, the fewer things can go wrong.

That probably sounds too simple to be useful. It isn’t.

Why code accumulates like debt

Software grows in one direction by default: bigger. A feature gets added, then another, then a third to patch a problem the second one created. Nobody sets out to build a bloated codebase. It just happens, the way a garage fills up over years of “I might need this someday.”

The problem is that code isn’t passive storage. Every line you add has to be read by other developers, tested by your CI pipeline, compiled by your build tools, and held in someone’s mental model when they’re trying to fix a bug at 11pm. Dead code (functions that are never called, configuration flags for features that shipped three years ago, entire modules written for a client who left) does all of that work while contributing exactly nothing to users.

This is the core insight: code has carrying costs that are proportional to quantity, not quality. A beautifully written function that does something you no longer need is still a tax on everyone who works in that codebase.

Diagram comparing a dense, highly connected node graph to a sparse, simpler one with fewer connections
Every module you remove eliminates all the interaction points it had with the rest of the system, not just the module itself.

The specific ways deletion prevents bugs

Let’s get concrete, because this is where the real argument lives.

Fewer interaction surfaces. Bugs rarely live in a single function. They live in the unexpected ways two pieces of code interact. Every module you add creates potential interaction points with every other module. This isn’t just intuition: complexity in software systems tends to grow faster than linearly with size, which is why bugs that only appear when a million people use your software are so hard to predict. Remove a module, and you eliminate all the interactions it was having with everything else.

Reduced cognitive load during debugging. When something breaks, you trace it. You follow the execution path, check the state at various points, form a hypothesis. If your codebase contains large sections you don’t fully understand because they were written by someone who left, or because they handle an edge case you’ve never triggered, your debugging search space is enormous. Deletion shrinks the territory you have to search.

Cleaner dependency graphs. Most non-trivial software depends on external libraries. Each dependency is a vector for bugs, security vulnerabilities, and breaking changes you didn’t choose. When you delete a feature, you often get to delete the libraries it pulled in. Fewer dependencies means fewer updates, fewer version conflicts, and fewer mornings where you discover that a library you use has a critical CVE.

Better test coverage. A test suite covering 80% of 10,000 lines leaves 2,000 lines untested. The same suite covering 80% of 5,000 lines leaves 1,000. All else being equal, a smaller codebase is easier to cover completely, and complete coverage is where you actually catch regressions before they reach users.

The counterintuitive cost of keeping things “just in case”

The argument you’ll hear against deletion is always some version of: “We might need that later.” It’s not an irrational concern. Rewriting something you deleted is real work. But this argument consistently underestimates the ongoing cost of keeping code around.

Think about what happens when a new engineer joins your team. They don’t know which code is active and which is vestigial. They read everything with equal attention. They write new code that (reasonably) assumes the old code is there for a reason. They build on top of it. Now your dead code has children.

Or consider what happens during a security audit or a major dependency upgrade. Every line of code is in scope. Dead code doesn’t get a pass just because it’s not actively running. Your team has to evaluate it, understand it, and make a decision about it. You’re paying that cost repeatedly, on every audit, every upgrade cycle, every onboarding.

The “just in case” instinct treats deletion as irreversible, but version control exists. If you genuinely need something back, it’s in your git history. The cost of retrieval is an afternoon. The cost of keeping it forever is measured in years of compounding friction.

What this looks like in practice

Deleting code well isn’t just about running a search for unused functions. Here’s a useful framework for thinking about what deserves to go:

Feature flags that are fully rolled out. If a feature shipped to 100% of users six months ago, the old code path protected by that flag is dead weight. Remove the flag and the fallback code.

Commented-out code. This is almost always “just in case” thinking materialized. If it’s important, it’s in version control. If it’s not in version control, it wasn’t important. Delete it.

Duplicate implementations. During fast growth, teams often build similar things independently. When you find two functions doing roughly the same thing, that’s not a sign you need both. It’s a sign you need one good one and a deletion.

Code serving users you no longer have. If you had an enterprise integration for a customer who churned two years ago, and no other customer uses it, the fact that the code still runs is not a reason to keep it. As the article on deleting a feature notes, the organizational resistance to removal is usually much stronger than the technical justification for keeping something.

The mindset shift that makes this stick

Most engineers are rewarded, at least culturally, for building things. Shipping a new feature is visible. Deleting old code is invisible. Nobody demos a removal in a sprint review.

But the engineers who make the biggest long-term impact on a codebase’s reliability are often the ones who resist the urge to add and instead ask: what can we take out? That kind of leverage is worth understanding.

If you want to start applying this today: pick one project you work on and spend an hour looking for code that hasn’t been touched in over a year and isn’t on a critical path. Read it carefully. Ask whether the codebase would be meaningfully worse without it. If the answer is no, you know what to do.