The most productive hour I ever spent was removing 500 lines of production code and watching our error rate drop by half. That deletion didn’t break anything. We’d shipped a feature that needed four fallback paths, each one handling an edge case that almost never triggered. The original engineer (me) had layered try blocks over exception handlers over silent continue statements until the logic resembled a plate of spaghetti thrown at a wall.
Our p95 latency was climbing, and nobody could explain why. I started by commenting out the entire handler block. Then I ran the test suite. Then I deployed to staging and let traffic flow for three hours.
That code wasn’t insurance. It was technical debt masquerading as safety. This is the cognitive trap every junior engineer falls into: more code equals more confidence. More error handling means more robustness. More abstraction layers mean more flexibility.
But each line you add creates surface area for bugs, increases mental overhead for every future reader, and hardens the system against change. My GPU cluster taught me this lesson painfully. Every inference pipeline I built started clean. Ten files, two hundred lines. Then grew arms and legs as I handled “what if” scenarios that never materialized in production.
Three concrete examples from my infrastructure: a request router that collapsed from 400 lines to 60 without losing throughput, an authentication middleware. That became unnecessary after a protocol redesign, and a retry loop whose complexity exceeded the value of all failures it prevented combined. Each one shared one property: deleting them made the system better than adding anything could have.
The Cognitive Cost of Unused Lines

The real enemy isn’t complexity — it’s useless complexity. Every extra function you keep compiles into mental overhead for every future developer, including your future self six months from now. The math is brutal. Each file in a repository adds a fixed cognitive cost before anyone reads a single line.
Open your IDE and look at the sidebar: every module, every class, every unused export demands attention. Your brain can hold roughly four unrelated things in working memory at once. A codebase with 400 functions means most of them exist outside that window, invisible until a bug surfaces. I learned this lesson rebuilding an internal deployment tool last year. The original had 1,200 lines across eight files.
After removing dead code paths and merging redundant abstractions, it ran at 340 lines across two files and did exactly the same job. No one missed the deleted code because no one had ever called most of those functions. Defect density follows lines of code with surprising consistency across open-source projects. More lines means more surface area for bugs, regardless of how well each line is written.
Every method you delete removes its own potential defects and the combinatorial explosion of interactions between that method and everything else. Think about it differently next time you write something new: what existing thing could vanish instead. That’s where the actual improvement lives. Not in another abstraction layer or configuration option nobody will ever toggle.
Every line you keep demands attention. It forces your brain to evaluate: is this condition reachable. Does this function have callers. Why does this variable exist. Each question consumes working memory and time.
Consider the impact on onboarding. New engineers can’t distinguish the critical path from historical debris without tracing execution manually. They waste days, sometimes weeks, handling dead branches in conditionals and abandoned feature flags. Every deprecated API endpoint left running expands your attack surface. Every third-party library imported for a single utility function increases build times and cache pollution.
Removing unused code isn’t about cleanliness. It’s about cognitive load reduction. Linters like ESLint with no-unused-vars, Java static analysis with SpotBugs, or Go’s unused tool catch most cases before they merge. But many teams disable these checks to silence warnings during sprints, letting technical debt accumulate silently over months.
You pay that tax every time you read the file. Every single time a developer opens it for the next decade. The best commit message I’ve ever written was three words: “Deleted two thousand lines.” No bugs filed against that change afterward. No regression needed either. The compiler caught everything that mattered.
Deletion Keeps the Interest Low
That commit message became a personal benchmark. Every subsequent feature request got measured against it: is this worth adding to the pile. Martin Fowler frames technical debt interest as compounding. Skip a refactor, patch around a legacy call, and next quarter’s upgrade costs double. I’ve watched teams spend three sprints updating dependencies because five untouched libraries sat in package.json, each pulling its own incompatible transitive mess.
Every file you keep charges rent. A utility module written in 2018 using deprecated patterns means every new developer must learn two APIs instead of one. The tests covering that dead path run for extra seconds on every CI push, multiplied by fifteen developers times twenty commits daily. AgileEngine’s post on technical debt modernization makes the same point bluntly: “Technical debt is by nature expensive.” They’re right, but they undersell it.
It’s not expensive like a loan; it’s expensive like a tax on every single interaction with your codebase.
Your best weapon against that tax is zero-tolerance deletion policy. Delete aggressively and your deployment pipeline stops being anxiety-driven theater where everyone holds their breath during upgrades. It becomes mechanical: remove old things, test new things, ship without ceremony. Technical debt doesn’t vanish through better architecture decisions tomorrow. It vanishes when you delete yesterday’s decisions today.
This is how technical debt accrues interest. Martin Fowler called it the cost of not refactoring. Every new feature takes longer because developers must understand, avoid, or work around the dead tissue. I worked on a microservice where every GET endpoint hit a legacy caching layer nobody could explain. We traced it: a three-year-old spike solution that never got removed. Deleting that single cache wrapper shaved 47 milliseconds off every response and removed six fragile integration tests.
Your CI pipeline doesn’t charge per build like AWS Lambda bills per millisecond-invocation. It charges in cognitive load per commit instead. The paradox: delete makes everything feel slower at first during deployment rollouts while you wait for migration rollbacks and cache invalidations for objects nobody used anyway. You remove scaffolding someone else depended on (or thought they did).
The missing-file anxiety when tail -f log/production.log no longer shows intermittent cache misses is your canary for unnecessary complexity. Hardware analogy from my friend who maintains datacenter PUE ratios: removing dead code isn’t defragmenting your SSD, which reduces seek latency by microseconds. It’s realizing you were booting two operating systems simultaneously on separate partitions both writing identical logs to different mount points and wondering why performance was shitty because interrupt handlers collided.
The Ownership Tax
That dual-boot analogy exposes the real problem. The cognitive bias runs deeper than any linter can reach. Psychologists call it the endowment effect: we value what we already possess far more than an identical unowned alternative. Applied to code, this means a developer will fight harder to keep their 400-line UserService.ts than they would to advocate for a colleague’s 40-line replacement that does exactly the same thing.
I’ve watched teams spend two hours in code review debating whether to keep one engineer’s switch statement, only to merge it unchanged because “it already works.” Ego attachment blinds us in measurable ways. Code review comments that start with “I think” rather than “the test fails here” are often ego defense, not technical judgment.
A senior developer at a past gig confessed they kept a dead handleLegacyAuth() function alive for six months simply because writing it had taken three all-nighters three years prior. The git blame showed 14 untouched commits.
Breaking this pattern requires explicit rituals, not good intentions. Enforce “no authorship review”: you cannot review code you wrote yourself. Others schedule quarterly “orphan hunts” where engineers delete entire directories without reading them first, relying entirely on CI failures to catch anything vital. The hardest lesson arrives when you delete your own work willingly.
That initial visceral sting, like throwing away a childhood keepsake, fades within minutes once you see the test suite pass faster and the diff count shrink by 67 lines of logic no human understood anymore. The best engineers eventually stop seeing old code as their children and start seeing it as scaffolding that overstayed its welcome.
I spent six months convincing myself I’d finish rewriting a microservice piece by piece. Thirty files, three directories, four integration points. Every file had my git blame. Every bug fix had my name on it. Kahneman’s endowment effect research shows we value what we already own 2x to 3x more than identical alternatives. Same applies to code you wrote last year.
That thirty-line auth middleware feels indispensable until you delete it and realize three lines of your web framework handle the same work. In October 2026, I finally deleted that entire microservice directory. The rewrite wasn’t a rewrite at all. The core logic collapsed into three functions: one for input validation, one for state transformation, one for output formatting. Total: 112 lines of Rust instead of 1,847 lines across thirty files.
That hurt for exactly two days. Then I couldn’t remember why the original needed its own database connection pool. Reframing helps: deletion isn’t admitting failure. It’s admitting your past self optimized for proving something instead of shipping something simple. The trick is timing. Delete on Tuesday morning when your brain is fresh and your ego is quietest (after coffee but before standup). Never delete at 4pm on Friday when you’re tired and every code path looks equally precious.
That microservice now lives as those three functions inside a parent project.
Build time went from 14 seconds to under two seconds per change because cargo doesn’t compile unused modules anymore. Your old code isn’t sentimental history to preserve in amber. It’s technical debt with emotional interest accrued daily. Delete it fast enough and the interest never compounds. Keep it long enough and you’ll protect the problem instead of solving it. Your career grows not by what you accumulate but by what you have the courage to let go.
One git rm at a time.
The same emotional attachment that makes you hoard code also makes you blind to the hidden dependencies that keep it alive. Before you delete anything, you need a systematic way to find every reference you didn’t know existed.
Work Backward from the Zero

So you want to remove code. Now find every hidden dependency first. A six-hour grep session beats a week of incident reviews. Run git log --all --follow on every file you’re removing. Uncovered more than one “dead” function still called by a cron job scheduled in 2019. The two commands that saved my team: ripgrep for symbol search and pyinspect for Python’s call graph traversal.
Shadow traffic catches what static analysis misses. Deploy an empty handler behind the same feature flag, wire it to a separate metric namespace, and watch real user behavior for 48 hours. If that shadow path registers zero hits but the live handler processes requests, congratulations. You found orphaned code living off network retries and legacy middleware behavior no one documented.
The most expensive removal I witnessed: a fifteen-line cleanup that broke three undocumented integrations because nobody checked the diff across deployment environments. The fire cost twelve engineers a Friday evening.
Build a systematic checklist before touching anything: - rg -l YOUR_FUNCTION_NAME across every repository your service touches. - Check dead letter queues, archival jobs, and any Lambda or function-as-a-service deployment tied to that endpoint. - Review dependency trees with your language’s equivalent of go mod why or Python’s pipdeptree. Tools matter less than the discipline to run them before typing git checkout.
One pattern our team adopted: commit deletions in their own branch named remove/feature-name, isolated from functional changes by at least twenty-four hours of production soak time. This kept bisect runs clean. If something broke downstream, we reverted one logical change instead of untangling thirteen files where someone fixed a linting error alongside the deletion. Delete knowing exactly which user would scream if you were wrong. Then prove they won’t have to.
The Only Metric That Matters
I deleted 12,000 lines last quarter. Code isn’t an asset you accumulate. It’s a liability you manage. Every line carries maintenance cost, cognitive load, and bug surface area. A hundred lines of dead code cost more than zero lines ever will.
The best engineers I’ve worked with share a quiet compulsion: they delete before they add. They resist the instinct to wrap broken logic in a if statement or patch around rot with a new abstraction layer. They pull the tooth. I’ve watched teams obsess over git blame like it measures productivity. Blame measures tenure and patience with cruft, not impact.
The most valuable commit is often invisible on that graph because it touched forty files but changed nothing users would see.
Delete strategically, not sentimentally. Batch SQL Server deletes in chunks of 5,000 rows inside WHILE loops. Deletion is the hardest habit to build. It feels like throwing away billable hours, like admitting you were wrong about the design. That feeling is a trap. Every line I kept out of fear — fear of breaking something, fear of an edge case, fear of being wrong — cost me more than it ever saved.
The router rewrite taught me that trust is earned through removal, not addition.
Keep Reading
- How I Would Break Into Tech in 2026 (It’s Not About Python)
- Voice-Controlled Multi-Agent Workflow for Claude Code in Tmux
- Beyond HCP Lock-In: Best Self-Hosted Secrets Management Alternative…
Here’s the question worth sitting with before your next feature: Does this code make the system easier or harder to understand tomorrow? If the answer wobbles, don’t add it. The best code I wrote last quarter was the 200 lines I deleted from our inference scheduler. Our p99 latency dropped by 11 milliseconds. Nobody noticed but me and my staging dashboard. That silence is the sound of use worth earning.