Last month I did something terrifying. I opened my main project — 47,000 lines of code I'd been working on for over a year — and deleted 42,000 of them.
The app didn't just survive. It got faster, more stable, and easier to maintain.
Here's what happened.
The Moment I Knew Something Was Wrong
I was trying to add a simple feature — a "sort by date" option. Should've taken 30 minutes.
It took me three days.
Not because the feature was complex, but because I had to navigate through:
- 6 abstraction layers
- 4 service classes that did almost the same thing
- 2 "utility" files with 800+ lines each
- A custom event system nobody asked for
- A homemade state management solution that made Redux look simple
I built all of this "just in case." Turns out, the case never came.
What I Deleted
1. Every Abstraction With Only One Implementation
I had interfaces for everything. IUserRepository, INotificationService, IAnalyticsProvider. Sounds professional, right?
Except each interface had exactly ONE implementation. I was never going to swap databases. I was never going to change analytics providers. I was writing code for imaginary future requirements.
Before: 3 files, 150 lines
After: 1 file, 40 lines
Same functionality.
2. The "Helper" and "Utils" Graveyard
My utils/ folder had 23 files. I ran a search for imports. 14 of those files were imported exactly ZERO times. The remaining 9 had functions that were used once each.
I inlined the used functions and deleted everything else.
Deleted: 1,200 lines of "helpful" utility code
3. Dead Feature Branches Merged Into Main
Three half-built features that were "almost done" six months ago. An A/B testing framework for an app with 12 users. A recommendation engine that recommended the same 3 items to everyone.
All of it: gone.
4. Defensive Code Against Impossible Scenarios
// Before
if (user && user.id && typeof user.id === 'string' && user.id.length > 0) {
if (user.email && typeof user.email === 'string' && user.email.includes('@')) {
if (user.role && ['admin', 'user', 'moderator'].includes(user.role)) {
// finally do something
}
}
}
// After
processUser(user); // validated at API boundary already
I was checking for things that literally could not happen because I validated data at the entry point. Every internal function was re-validating the same data.
5. Comments That Explained Obvious Code
// Before
// Increment counter by one
counter += 1; // adds 1 to counter
// After
counter += 1;
I'm not even joking. I had hundreds of these.
The Results
| Metric | Before | After | Change |
|---|---|---|---|
| Lines of code | 47,000 | 5,200 | -89% |
| Build time | 45s | 8s | -82% |
| Test suite | 12min | 90s | -87% |
| Time to add feature | 2-3 days | 2-3 hours | ~90% faster |
| Bugs per week | 3-5 | 0-1 | -80% |
The Lesson Nobody Teaches You
Every tutorial, every course, every "best practices" article teaches you to ADD things:
- Add abstractions
- Add design patterns
- Add error handling
- Add logging
- Add types
Nobody teaches you to DELETE.
But deletion is the most powerful tool in a developer's arsenal. Every line of code is a liability. Every abstraction is a decision that future-you has to understand.
The best code isn't clever code. It's code that doesn't exist.
My New Rules
- No abstraction until the third use. First time: write it inline. Second time: copy-paste is fine. Third time: NOW extract it.
- Delete before adding. Before writing new code, look for code to remove.
- Unused code is dead code. If it's not imported anywhere, it doesn't exist. Delete it.
- Validate at the boundary. Check input once when it enters your system. Trust it internally.
- Features have expiration dates. If nobody used it in 3 months, kill it.
This experience fundamentally changed how I approach software. I now focus on building small, focused tools instead of complex systems. I share more practical insights like this at boosty.to/swiftuidev.
Top comments (0)