Skip to main content

Command Palette

Search for a command to run...

Why I Deleted 10K Lines of Code and App Got Better

Learn: Why I Deleted 10K Lines of Code and App Got Better

Updated
12 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Why I Deleted 10,000 Lines of Code and My App Got Better

The moment I realized I'd been building a monument to my own confusion

It was 2 AM on a Tuesday when I finally admitted it to myself: my "elegant" codebase was a disaster.

I was staring at a bug report that should have taken 10 minutes to fix. Three hours later, I was still hunting through nested abstractions, custom frameworks, and "future-proof" architecture that was supposed to make everything easier. My coffee had gone cold. My eyes burned. And somewhere in those 47 files I'd opened, the simple truth hit me:

I had no idea what my own code did anymore.

That night, I made a decision that would change how I think about software forever. I started deleting. And I didn't stop until 10,000 lines were gone.

The Seduction of Complexity

Let me take you back to where this story really begins.

Two years earlier, I'd just landed my first senior developer role. I was hungry to prove myself, armed with design patterns from that thick book everyone recommends but nobody finishes, and absolutely convinced that "real" engineers built systems that could handle anything.

My project was straightforward: a content management dashboard for a mid-sized marketing team. They needed to schedule posts, track analytics, and collaborate on campaigns. Simple stuff.

But simple wasn't impressive enough for me.

I built an event-driven architecture with a custom message bus. I created an abstraction layer for the abstraction layer. I implemented a plugin system for features nobody asked for. Every class had an interface. Every function had a factory. I even built a proprietary ORM because "the existing ones weren't flexible enough."

My code reviews were legendary. "Wow, this is really sophisticated," colleagues would say, which I took as a compliment. Looking back, I realize they were just being polite. What they meant was: "I have no idea what's happening here, but you seem confident."

The app worked. Mostly. When it didn't, fixing it required a PhD in understanding my own abstractions.

The Cracks Start Showing

The problems started small.

A new feature request came in: "Can we add a simple duplicate button for campaigns?"

Should've been a one-hour task. It took three days.

Why? Because duplicating a campaign meant navigating through my beautiful, over-engineered system:

  • The CampaignFactory that validated against seventeen different rules
  • The EventBus that notified twelve different subscribers
  • The PermissionDecorator that wrapped the AuthorizationStrategy
  • The DataMapper that translated between four different object representations

Each layer made sense in isolation. Together, they were quicksand.

Then came the performance issues. The dashboard was slow. Not unusably slow, just... annoying. Loading a list of campaigns took 3 seconds. I spent weeks optimizing database queries, adding caching layers, implementing lazy loading. I made it faster, but also more complex.

The real problem? I was caching data that didn't need to exist in the first place.

The Breaking Point

The moment of truth came during an incident I'll never forget.

Our biggest client called at 4 PM on a Friday. Their campaign scheduler had stopped working. Posts weren't going out. They had a product launch happening over the weekend. This was a five-alarm fire.

I dove into the code. The scheduler used my custom job queue system, which interfaced with my event bus, which triggered my notification pipeline, which... you get the idea.

After two hours of debugging, I found the issue: a null check I'd forgotten in a decorator class that wrapped another decorator class that implemented an interface that extended another interface.

The fix was literally adding two characters: ?.

But finding those two characters required understanding a system so convoluted that even I, its creator, got lost in it.

As I deployed the fix and watched the client's campaigns finally go out, I felt something I'd never felt before about my own code: shame.

Not because I'd made a mistake. We all make mistakes. But because I'd built a system where a simple mistake became an archaeological expedition.

The Purge Begins

That weekend, I did something crazy. I created a new branch called simplify-or-die and started cutting.

First to go: my custom ORM. 1,200 lines deleted. Replaced with a simple query builder that everyone on the team already knew. The code became immediately more readable.

Then: the event bus. Another 800 lines gone. Turns out, most of those "events" were just function calls that should've been... function calls.

The plugin system? Nobody had ever written a plugin. 1,500 lines deleted.

The abstraction layers? I had interfaces with single implementations. Classes that existed only to instantiate other classes. Factories that created factories. All of it, gone.

I kept going:

  • Deleted the custom validation framework (500 lines) → used simple conditionals
  • Removed the strategy pattern implementations (600 lines) → used straightforward if/else
  • Eliminated the decorator chains (900 lines) → wrote direct functions
  • Stripped out the "flexible" configuration system (1,200 lines) → used environment variables

Every deletion felt like removing a weight from my shoulders.

The Resistance

Monday morning, I showed my changes to the team. The reaction was... mixed.

"But what about scalability?" asked Tom, our junior dev who'd been studying the same design pattern books I had.

"What about it?" I replied. "We have 50 users. We're optimizing for problems we don't have."

"This seems too simple," said Sarah. "What if requirements change?"

"Then we'll change the code. That's what code is for."

The resistance was real, and I understood it. We'd all been taught that good code meant complex code. That abstraction was always better. That you should build for the future.

But here's what I'd learned: the future is unknowable, and complexity is a debt you pay every single day.

The Results

Two weeks after merging my changes, something remarkable happened.

The bug report rate dropped by 60%. Not because I'd fixed more bugs, but because there were fewer places for bugs to hide.

The onboarding time for new developers went from two weeks to three days. The codebase was now small enough to understand in an afternoon.

Performance improved across the board. The campaign list that took 3 seconds? Now loaded in 400ms. Not because of clever optimization, but because we weren't doing unnecessary work anymore.

But the most surprising change was in the team's velocity. Feature requests that used to take days now took hours. We shipped more in the next month than we had in the previous three.

What I Learned About "Less"

Deleting those 10,000 lines taught me lessons that no book or course ever could.

Lesson 1: Complexity is a choice, not a requirement

Every abstraction I'd added was a choice. I chose to make things complicated because I thought that's what good engineers did. But the best engineers I know now? They make things simple.

Lesson 2: Code is a liability, not an asset

Every line of code is something that can break, something that needs to be understood, something that must be maintained. The best code is the code you don't write.

Lesson 3: Abstraction should eliminate complexity, not hide it

Good abstractions make things simpler. If your abstraction requires documentation, helper classes, and a deep understanding of patterns, it's not an abstraction—it's obfuscation.

Lesson 4: Solve today's problems today

I'd spent months building for hypothetical futures. "What if we need to support multiple databases?" We didn't. "What if we need to scale to millions of users?" We had hundreds. All that "future-proofing" just made the present harder.

Lesson 5: Boring is beautiful

The most reliable, maintainable code I've ever written is boring. It's obvious. It's the code where you read it and think, "Well, of course that's how it works." There's no cleverness to admire, no patterns to decode. Just clear logic doing exactly what it needs to do.

The New Philosophy

These days, I write code differently. Before adding anything, I ask myself:

  • Do I need this right now? Not "might I need this someday," but "is this solving a problem I have today?"

  • Can I do this with less? Before reaching for a library or pattern, can I just write the straightforward solution?

  • Will this make sense in six months? If I came back to this code with no context, would I understand it?

  • Am I building this to solve a problem or to feel smart? This one's hard to admit, but it's often the most important question.

I've also learned to love deletion. Every few months, I do a "code purge" where I actively look for things to remove. Dead features, unused abstractions, "temporary" workarounds that became permanent. It's like cleaning out a closet—you always find more than you expected.

The Pushback You'll Face

If you try this approach, people will resist. I still get pushback. Here's what they'll say and how I respond:

"But this isn't scalable!"

  • "Show me the metrics that say we need to scale this. Let's solve real problems, not imaginary ones."

"This violates [design pattern/principle]!"

  • "Patterns are tools, not rules. If following a pattern makes the code worse, the pattern is wrong for this situation."

"What if requirements change?"

  • "Then we'll change the code. Simple code is easy to change. Complex code is easy to break."

"This seems too basic for a senior engineer."

  • "Senior engineers make things simple. Junior engineers make things complex to prove they're smart."

That last one stings, but it's true. I was that junior engineer with a senior title.

The Metrics That Matter

Here's what actually improved after my great deletion:

  • Time to fix bugs: Down 65%
  • Time to add features: Down 70%
  • Lines of code per bug: Down 80%
  • New developer productivity: Up 300%
  • Deployment confidence: Immeasurable improvement

But the most important metric wasn't quantifiable: I enjoyed working on the codebase again.

I wasn't afraid to make changes. I didn't dread bug reports. I could hold the entire system in my head. That feeling of control and understanding? That's what good code should give you.

When Complexity IS Needed

I'm not saying all complexity is bad. Some problems are genuinely complex and require sophisticated solutions.

But here's the thing: those problems are rarer than you think.

In my career, I've worked on:

  • A real-time trading system (genuinely complex)
  • A machine learning pipeline (genuinely complex)
  • A distributed data processing system (genuinely complex)

And also:

  • Seventeen CRUD apps (not complex)
  • Nine dashboards (not complex)
  • Twelve REST APIs (not complex)

Most of what we build doesn't need to be complicated. We make it complicated because we're bored, or insecure, or trying to make our resume look impressive.

The trading system needed careful architecture. The CRUD apps needed clear, simple code that did exactly what it said on the tin.

The Hardest Part

You know what the hardest part of deleting all that code was? Not the technical challenge. Not convincing my team. Not even the fear of being wrong.

It was admitting that I'd wasted time.

Those 10,000 lines represented months of work. Late nights. Weekends. Careful thought and deliberate design. Deleting them meant admitting that all that effort had made things worse, not better.

That's a hard pill to swallow.

But here's what I've learned: sunk cost is sunk. The time I spent writing bad code is gone whether I keep it or delete it. The only question is: do I want to keep paying the price for that mistake?

Every day you keep bad code is another day of slower development, more bugs, and harder maintenance. The sooner you cut your losses, the sooner you stop losing.

What I'd Tell My Younger Self

If I could go back and talk to that eager senior developer about to build his monument to over-engineering, here's what I'd say:

Start with the simplest thing that could possibly work. Not the simplest thing that could work if you squint and assume perfect conditions. The actual simplest thing. Then stop.

Resist the urge to be clever. Clever code is fun to write and hell to maintain. Boring code is boring to write and a joy to maintain. Choose boring.

Every abstraction is a bet. You're betting that the complexity of the abstraction will be less than the complexity it eliminates. Most of the time, you'll lose that bet.

Delete more than you add. If you're not regularly deleting code, you're accumulating cruft. Make deletion a habit, not an exception.

Your job isn't to write code. Your job is to solve problems. Sometimes the best solution involves writing code. Sometimes it involves deleting code. Sometimes it involves not writing code at all.

The Ongoing Journey

I still catch myself over-engineering sometimes. Old habits die hard. Just last week, I started building a "flexible" configuration system before stopping myself and asking: "Do I need this, or do I just want to build it?"

I didn't need it. I wanted to build it because it was interesting. So I didn't.

The difference now is that I notice. I question. I delete.

My codebase is smaller than it was two years ago, even though we've added dozens of features. That's not because I'm writing less—it's because I'm deleting more.

Every feature I add, I look for two things to remove. Every abstraction I create, I delete three old ones. The codebase doesn't grow—it evolves.

The Real Lesson

Here's the truth that took me 10,000 deleted lines to learn:

Software development isn't about building. It's about maintaining.

You'll spend 10% of your time writing new code and 90% of your time reading, understanding, and modifying existing code. Every line you write is an investment in that 90%.

Complex code is a bad investment. It costs more to understand, more to modify, more to debug, and more to replace. Simple code is a good investment. It pays dividends every time someone reads it.

The best code I've ever written is code that makes the next developer think, "Well, obviously." Not "Wow, this is clever," but "Of course, what else would it be?"

That's the code that lasts. That's the code that scales. That's the code that makes your app better.

Your Turn

If you're sitting on a codebase that's too complex, too clever, too "architected," I have one piece of advice:

Start deleting.

Not everything at once. Not recklessly. But start.

Find one abstraction that doesn't pull its weight. Delete it.

Find one "flexible" system that nobody uses. Delete it.

Find one clever pattern that makes simple things hard. Delete it.

You'll be scared. You'll second-guess yourself. You'll worry about the future.

Do it anyway.

Because here's what I know now: the future you're worried about? It's easier to build with simple code than complex code. Always.

The app I have today is better than the one I had two years ago. Not because I added 10,000 lines of brilliant code, but because I deleted 10,000 lines of complicated code.

Less really is more.

And sometimes, the best code is the code you have the courage to delete.


Now if you'll excuse me, I have a codebase to review. I bet I can find at least 100 lines to delete.