#The Problem
Abstraction sounds simple early on: avoid duplication, keep things DRY, extract common logic.
That works, until you’ve maintained a system long enough to see the downside. Too much abstraction makes code harder to follow, harder to change, and oddly more fragile. Too little, and things drift apart in subtle ways.
The real problem is that not all duplication means the same thing.
Sometimes duplication points to a shared concept. Other times, it’s just a coincidence, two pieces of code that look similar today but won’t evolve the same way. Abstracting too early couples them together, and that’s where things break down.
#When Abstraction Goes Wrong
A bad abstraction doesn’t stay neutral. It attracts complexity. You’ll see more parameters added over time, conditionals to handle edge cases, and call sites that are harder to understand than the original duplicated code.
At that point, the abstraction isn’t helping, it’s hiding complexity in a worse place.
Because of that, the main thing I optimize for isn’t how “clean” the code looks. It’s this:
How easy will this be to change later?
#When I Abstract Early
I don’t avoid early abstraction entirely. I’ll introduce it once the problem's shape is clear.
A few signals I look for:
- The structure is consistent and unlikely to diverge
- The variations are predictable
- I can name the abstraction clearly
If I can’t give something a clean, meaningful name, that’s usually a sign I’m forcing it. I’ll also abstract early when I know change is coming, like when multiple implementations or swappable behavior are involved. The key is keeping it minimal. Not “future-proof,” just enough to support what’s clearly ahead.
#When I Keep It Simple
There are just as many cases where I won’t abstract. If the similarity is superficial, I leave it alone. Duplication is cheaper than the wrong abstraction. If the code is still changing quickly, I wait. Early abstractions tend to reflect assumptions that don’t hold up. And if the abstraction makes the code harder to read, it’s not worth it. Clear code at the call site matters more than reducing duplication behind the scenes.
#What I Avoid
A few things I try not to do:
- Over-“future-proof” code for problems that don’t exist yet
- Force DRY when duplication is harmless
- Apply patterns just because they’re patterns
Patterns and abstractions should come from the problem, not the other way around.
#Closing Thought
Abstraction isn’t about removing duplication. It’s about managing change. Sometimes that means introducing structure early when the direction is clear. Other times, it means letting the code stay simple until the pattern proves itself. The hard part isn’t writing the abstraction. It’s knowing when it’s actually justified.