Abstraction and Leaky Abstractions
Date: 2026-08-17
An abstraction hides detail so you can work at a higher level. All non-trivial abstractions leak — the hidden detail reappears as a bug, a performance cliff or an error message in vocabulary you were supposed to have been spared.
An abstraction presents a simplified interface over something more complex, letting you use it without understanding its internals.
A leaky abstraction is one where the hidden complexity becomes visible again, usually at the worst moment. This is the normal state, not a defect in a particular abstraction — Joel Spolsky’s formulation is that all non-trivial abstractions, to some degree, are leaky.
What a leak looks like
THE ABSTRACTION SAYS THE LEAK
"just call user.orders" → 400 queries
"files are files" → network files
fail in ways
local files never do
"TCP is a reliable → but throughput
byte stream" collapses on a
lossy link
"SQL is declarative" → until it's slow,
and now you're
reading a query
plan
"the ORM handles it" → until a migration
locks a table for
four minutes
See: N+1 Queries · TCP and UDP · Query Planning
Each leak forces you down a level. The abstraction saved you learning that level right up until the moment it required you to know it and to know how the abstraction maps onto it — which is more work than knowing it directly.
What abstraction genuinely buys
Being fair to it, because the leak framing can read as cynicism:
- Working at a useful scale. Nobody writes a retail site in assembly, and the abstractions between are the reason
- A shared vocabulary. “Cache”, “queue”, “index” carry a lot in one word
- Substitutability. A good interface lets the thing behind it be replaced
- Fewer decisions. Sensible defaults are an abstraction over a decision tree
When to add one
The test that separates useful abstraction from ceremony:
ADD ONE WHEN
the same pattern appears three or
more times
the underlying thing genuinely varies
and you have TWO real cases
the detail below is genuinely
irrelevant to callers
DON'T WHEN
you anticipate variation that hasn't
arrived
it exists to look tidy
the abstraction has one implementation
and always will
the wrapper is thinner than the thing
it wraps
“We might swap the database one day” has produced more useless repository layers than any other sentence in software. The second implementation almost never arrives, and the abstraction constrains the one you have to the intersection of features every candidate supports.
The cost of a wrong one
A wrong abstraction is more expensive than duplication, and this is the counterintuitive part.
DUPLICATION WRONG ABSTRACTION
obvious looks correct
change n places every caller now
depends on it
easy to find each new case adds a
parameter or a flag
→ control coupling
easy to remove removing it means
untangling n callers
The honest move on discovering a bad abstraction is to inline it back — restore the duplication, then re-extract along the right seam. That feels like going backwards and usually isn’t.
Living with leaks
Since you can’t avoid them:
- Understand one level below the one you work at. Not every level — one. Enough to recognise the leak’s shape when it appears
- Read the generated artefact. The SQL your ORM produces, the bundle your build emits, the DOM your framework renders. Leaks are visible there before they’re visible in production
- Learn the leaks of your main tools specifically. Every abstraction has a known set, and they’re the things experienced practitioners check first
- Prefer abstractions that let you drop down. A library exposing an escape hatch to the underlying primitive is far more useful than one that seals it off — Component API Design
- Treat a surprising performance cliff as a leak, and go looking for the level it came from. That reframing shortens most debugging
The version to carry
Abstractions are load-bearing and provisional at once. Use them, expect them to leak, and know enough about what’s underneath to recognise the leak when it happens — that’s the whole practical position.