Tags: web-dev concept

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

See: Coupling and Cohesion

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.