The hardest part of software design isn't adding features — it's knowing what to leave out. Simplicity is a discipline, not a default. Left to entropy, every system trends toward complexity. It takes deliberate effort to keep things simple.
Complexity is a debt
Every line of code you write is a liability. It needs to be read, understood, maintained, and eventually changed. The more code you have, the harder all of those things become.
I've seen projects collapse not from a single bad decision, but from the accumulated weight of hundreds of small ones. Each individually reasonable. Together, suffocating.
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. — Antoine de Saint-Exupery
This isn't just a nice quote. It's an engineering principle. The most elegant solutions I've encountered — in code, in products, in organizations — are the ones that do less, better.
The three questions
Before adding anything to a system — a feature, an abstraction, a dependency — I ask three questions:
- What problem does this solve? If you can't articulate the problem clearly, you're not ready to build the solution.
- What's the simplest thing that could work? Not the cleverest. Not the most scalable. The simplest. You can always add complexity later; removing it is much harder.
- What's the cost of not doing this? Sometimes the answer is "nothing much." That's a strong signal to wait.
These questions have saved me from more unnecessary abstractions, premature optimizations, and over-engineered solutions than any design pattern ever has.
The abstraction trap
There's a seductive pull toward abstraction in software. We see duplication and our instinct says "abstract it." We imagine future requirements and our instinct says "make it flexible."
But premature abstraction is more dangerous than duplication. Duplication is obvious and easy to fix when you understand the pattern. A wrong abstraction, on the other hand, becomes a foundation that other code builds on. Fixing it means rethinking everything above it.
I've adopted a simple rule: wait until you've seen a pattern three times before abstracting it. The first two times, you don't have enough information. The third time, the right abstraction usually reveals itself.
Simplicity in practice
What does simplicity look like day to day? A few patterns I've found valuable:
- Flat over nested. Deeply nested structures — whether in code, file systems, or organizations — are harder to navigate and reason about.
- Explicit over implicit. Magic is the enemy of understanding. Make the important things visible.
- Fewer, better dependencies. Each dependency is a bet on someone else's priorities, maintenance habits, and security practices. Choose carefully.
- Names that explain. If a variable, function, or module needs a comment to explain what it does, it needs a better name.
The courage to delete
Perhaps the most underrated skill in software is the willingness to delete code. Not archive it. Not comment it out. Delete it.
Version control means nothing is truly lost. But psychologically, we cling to code we've written. We remember the effort it took. We imagine the future scenario where we might need it again.
We almost never do.
The most productive refactoring sessions I've had weren't about rewriting code — they were about removing it. Every line deleted is a line that never needs to be debugged, documented, or explained to a new team member.
Simplicity isn't the easy path. It requires saying no to good ideas in service of great ones. It demands the humility to admit you don't know what the future holds, and the discipline to build only what you need today. But the reward — systems that are a joy to work in, products that users actually understand, teams that move fast because they're not fighting their own tools — is worth every "no" along the way.