Gunjan Sharma

Career · Lessons Learned

Why I Deleted 12,000 Lines of Code in My First Week and Nobody Fired Me

· Updated

I joined the team on a Monday. By Friday, I had deleted 12,000 lines of code. And somehow, I still had a job.

This is not a story about being reckless. It is a story about what happens when you actually read a codebase instead of just running it — and what you find when you do.

The First Day

I was brought in as a backend engineer to help scale the platform. The stack was Node.js, MySQL, and a monolith that had been growing for four years without a single major refactor. My first task was simple: add a new API endpoint for user portfolio summaries.

I opened the repo. It had 94,000 lines of code across 380 files.

I started reading.

By Tuesday afternoon, something was bothering me. Files were being imported but never called. Services were registered in the DI container but had no routes pointing to them. There were entire folders — legacy/, archive/, old-payments/ — sitting right inside src/, fully imported in the main entry file.

The Dead Code Audit

I started cataloguing what I found. Not guessing — actually tracing call chains. I used a combination of VS Code's "Find All References" and a manual dependency graph I built in a Notion doc.

Here is what I found after three days:

- 11 services that were imported in app.js but had zero routes attached

- 3 complete payment modules from before a provider migration in 2021

- A folder called analytics-v1/ that contained 2,400 lines of custom analytics code — all replaced by a third-party SDK two years ago

- 47 utility functions in utils/helpers.js with no callers anywhere in the codebase

- 6 database migration files that were never run (sitting in /migrations/pending which was gitignored)

- A full implementation of a feature called "Group Investments" — about 1,800 lines — commented out with a note that said // re-enable after compliance review dated January 2019

I totalled it up. About 12,200 lines of code that were either unreachable, commented out, or actively importing dead dependencies.

The Anxiety of Deletion

Here is the thing nobody tells you about deleting old code: it is terrifying.

Every time you delete a file, a small voice in your head says: what if someone uses this? What if it is called dynamically somewhere? What if deleting this breaks a cron job that runs once a month and nobody will notice until month-end?

Those fears are valid. Dead code is not always obviously dead.

So I built a process before I deleted a single line.

Step 1: Static analysis first. I ran a tool called ts-prune (we had TypeScript configured but barely used it — the .ts files were mostly just renamed .js). It gave me a list of exported symbols with no known importers. That was my starting list.

Step 2: Runtime coverage. I spun up the app locally, hit every route I could find in Postman, and ran Istanbul (nyc) for coverage. Anything with 0% coverage across all test runs moved up on my list.

Step 3: Git blame. For anything I was unsure about, I ran git log --follow -p -- filepath to see who last touched it and why. Commit messages like "archiving old payment module" gave me confidence. Messages like "WIP" made me leave it alone.

Step 4: Grep for dynamic requires. In Node.js, you can do require(someVariable) which static analysis won't catch. I grepped the entire codebase for require( patterns where the argument was not a string literal. Found four — none of them touched the files I planned to delete.

Step 5: Delete in branches, with tests. I did not delete everything at once. I made 8 separate PRs, each targeting one logical group of dead code. Each PR included a before/after line count and a brief description of what was removed and why.

What Actually Happened When I Deleted It

Three things happened that I did not expect.

First, the test suite ran faster. We went from 4 minutes 20 seconds to 2 minutes 55 seconds. Turns out, some of the dead service files were being instantiated during the test setup — even though they had no routes — and they were making real database connections in the test environment.

Second, we found a bug. Inside the old analytics module, there was a scheduled job that ran every 6 hours and wrote aggregated data to a table called analytics_cache. That table still existed in the database. Other parts of the system were reading from it — specifically, an admin dashboard query. When I deleted the analytics module and the job stopped running, the admin dashboard started showing stale data. We caught it in staging. Fixed it by replacing the dependency with the actual SDK data source.

Third, onboarding documentation got written. After the cleanup, I wrote a 3,000-word architecture doc explaining what each folder actually did. This became the most-read internal document in the company Slack within the first week. People did not know what half the code was for either.

What I Learned About Reading a Codebase

Most engineers treat a new codebase like a sprint: clone, run, open the file you need, make the change. I used to do this too.

After this experience, I treat a new codebase like an archaeological dig. You are not just here to add a feature. You are here to understand what happened here before you. The code is a record of decisions — some good, some bad, some made under time pressure at 11pm before a launch.

Dead code is not laziness. It is history. It is the payment provider that got swapped. The feature that compliance killed. The analytics tool that got replaced but nobody updated the imports.

The engineers who wrote that code were not incompetent. They were just moving fast, and nobody ever gave them the time to go back and clean up.

That is where you come in.

Practical Takeaways

If you are in a similar situation — new to a codebase, something feels off — here is what I recommend:

Do not delete anything in the first week without evidence. Use ts-prune, depcheck, or madge to build a dependency graph first. Look for islands — nodes with no incoming edges.

Always check for dynamic requires and eval. Static analysis will miss these. A simple grep saves you from a production incident.

Run git blame before you delete. A file that was last touched two weeks ago is different from a file last touched in 2020. Recency matters.

Delete in small, reviewable batches. One logical group per PR. Give your teammates a chance to say "wait, that is used in the mobile app" before it is merged.

Write the architecture doc after cleanup, not before. You will understand the codebase far better after you have traced what is actually alive in it.

The job interview question "how do you approach a new codebase" has a boring answer and a real answer. The boring answer is: read the README, run the tests, look at the main entry point. The real answer is: find the dead things. They will tell you more about how the system actually evolved than any documentation ever will.