There’s a moment every senior engineer knows well. You open a pull request, spend two hours reviewing it, and your only comment is: ‘Can we just delete this entire module?’ The junior developer who wrote it stares at their screen, confused. They wrote clean code, it’s well-documented, it passes all the tests. And yet the most experienced person in the room is asking for it to be removed entirely. This isn’t cruelty. It’s craft.

This instinct, to reach for the delete key before the keyboard, sits at the heart of what separates good software engineering from great software engineering. And it’s something the industry rarely talks about openly, partly because software engineering is genuinely hard to explain to outsiders and partly because productivity metrics almost always reward addition over subtraction.

The Codebase as a Living Organism

Think of a codebase the way you’d think of a garden, not a building. Buildings are additive. You pour a foundation, raise walls, add floors. Gardens are different. A garden that you only ever plant in, never prune, never weed, eventually becomes something you can’t walk through.

Code accumulates technical debt in exactly this way. Technical debt is the engineering term for shortcuts, workarounds, and outdated patterns that made sense at the time but now slow everything down. The debt compounds. A function written to handle one edge case spawns three more functions to handle the edge cases of the edge case. A configuration flag added to support a client who left two years ago still lives in the codebase, forking the logic in ways nobody fully understands anymore.

The industry has known for decades that the cost of maintaining messy code is enormous. As we explored in our piece on why fixing a software bug costs 100x more than preventing it, the longer a problem persists in a system, the exponentially more expensive it becomes to address. Dead code and orphaned modules are bug incubators. They don’t just sit there harmlessly, they confuse future developers, create false dependencies, and occasionally trigger failures nobody can trace.

Why Writing Code Feels More Productive Than Deleting It

Here’s a psychological puzzle. If deletion is so valuable, why does it feel so unsatisfying?

Because almost everything in software engineering is measured by what gets added. Lines of code, features shipped, tickets closed. Version control systems like Git show you exactly how many lines you wrote this week. Pull requests get reviewed and merged. There’s a ritual satisfaction to adding something to the world.

Deletion has no equivalent ceremony. You remove a thousand lines of code and the diff is red, all red, and it looks like you’ve done something destructive. Your manager’s dashboard probably shows negative output. And yet the codebase is now faster, easier to understand, and cheaper to maintain.

This is related to a broader problem in engineering culture, where busyness gets mistaken for productivity. The same way multitasking apps were designed to make you focus on one thing, the best codebases are engineered to do one thing well, not seventeen things adequately.

The Practical Mechanics of Deletion

So what does principled deletion actually look like in practice? There are a few common scenarios.

Dead code removal is the most obvious. Dead code is code that exists in the repository but is never executed. It might be a function nobody calls, a feature flag that’s been set to false for eight months, or an entire class that got replaced but never cleaned up. Static analysis tools (programs that read your code without running it) can find most of this automatically. The hard part is convincing the team it’s safe to delete, because someone always says ‘what if we need it later.’ The answer, almost always, is: it’s in version control. You can get it back in thirty seconds if you ever need it.

Abstraction collapse is more nuanced. Early in a project, developers often over-engineer things. They build elaborate abstractions, layers upon layers, to handle flexibility they imagine they’ll need. Then the product evolves in a completely different direction and all that flexibility is now just complexity. A senior engineer looks at this and says, ‘We have five classes to do what one function could do.’ Collapsing that abstraction back down is deletion in spirit even when the line count doesn’t shrink dramatically.

Dependency pruning is the one that bites companies hardest. Every external library you import is code someone else wrote, code you’re now responsible for understanding, updating, and securing. A Node.js project’s node_modules folder can contain hundreds of packages you never intentionally chose. Regularly auditing and removing unused dependencies isn’t glamorous, but it directly reduces your attack surface (the number of ways someone could exploit your system) and speeds up your build times.

What This Teaches You About System Design

There’s a deeper principle underneath all of this, which is that the best systems are not the ones with the most features but the ones with the fewest moving parts that still accomplish the goal.

This is why bugs multiply as teams grow, not because developers get worse, but because every new feature adds new interactions, and interactions are where bugs live. Two modules that each work perfectly can fail catastrophically when combined. The fewer modules you have, the fewer combinations exist to go wrong.

This principle even shows up in how experienced engineers write comments. When a developer writes a comment explaining what a block of code does, it can sometimes be a sign that the code itself should be rewritten to be clearer. As we covered in why engineers write code comments for themselves, not for you, comments are often a symptom, not a solution. The best refactor removes the need for the comment entirely by making the code self-explanatory.

The Senior Engineer’s Mental Model

When a senior engineer looks at a codebase, they’re running a constant mental calculation: what is the cost of this code existing versus the benefit it provides? This is different from asking whether the code works. Working code can still be a liability.

Every line of code has a carrying cost. Someone has to understand it during onboarding. Someone has to update it when the language version changes. Someone has to test it after every deployment. Someone has to debug it at 2am when it misbehaves in a way nobody anticipated.

The engineer who writes one elegant function to replace three brittle ones isn’t doing less work. They’re doing harder work. They’re thinking more carefully, making more precise decisions, and accepting the discomfort of appearing less productive by conventional metrics.

The best commit message a senior developer can write is often just: ‘Remove unnecessary code.’ No feature, no bugfix, no ticket number. Just the quiet satisfaction of a codebase that got a little lighter, a little faster, and a little easier to understand. That’s the work. It just doesn’t look like it from the outside.