Skip to the content.

The cost of premature abstraction: wrong is more expensive than repeated

The cost of premature abstraction: wrong is more expensive than repeated

Seeing two similar pieces of code and pulling them into a shared abstraction feels like the responsible, DRY thing to do. Done too early — before you actually know how the two cases will diverge — it is a trap. You build an abstraction around the coincidental similarities you can see now, and then reality reveals the differences you could not, so you bolt on flags and special cases until the shared thing is more tangled than the duplication would ever have been. The uncomfortable truth is that a wrong abstraction is more expensive than repeated code, because duplication is easy to see and delete, while a bad abstraction is load-bearing and everyone is afraid to touch it.

Duplication is cheap to fix later; a wrong abstraction accretes flags Left: two similar copies, easy to merge or delete later. Right: an early abstraction that grows boolean flags and special cases as the cases diverge, becoming tangled. duplication copy A copy B visible, easy to merge or delete early abstraction shared() if (isA) …if (variant) …if (legacy) … tangled, nobody dares touch it
Two copies are legible and cheap to reconcile once you see the real shape. An abstraction built too early grows a flag per divergence until it is the scary part of the code.

The wrong abstraction grows flags

Watch what happens when you abstract two things that are only superficially alike. Every way they turn out to differ becomes a parameter, and the “shared” function becomes a switchboard nobody understands:

// abstracted after seeing two similar cards — then reality added flags
function renderCard(item, { showAvatar, isCompact, hasFooter, variant, legacyMode }) {
  // 60 lines of `if (isCompact) … else if (legacyMode) …`
  // each flag was a real divergence the original abstraction didn't foresee
}

Each flag was a moment where the cases diverged and you patched the abstraction instead of admitting it was wrong. The result is harder to change than two honest copies would have been.

Duplication is cheaper than the wrong shape

The counter-move is to tolerate duplication until the real abstraction reveals itself. Two or three similar copies are fine — they are easy to read, easy to change independently, and easy to merge once you can see what they truly share. “Write Everything Twice” before you extract is a real heuristic: the third occurrence is usually when the genuine common shape becomes visible, and only then is the abstraction likely to be right:

// two honest, separate cards — each simple, each free to diverge
function UserCard({ user })   { return <article className="card"></article>; }
function ProductCard({ item }) { return <article className="card"></article>; }
// merge them ONLY when a third case shows what's actually shared — not before

Prefer the abstraction that is easy to unwind

When you do abstract, favour shapes that are cheap to back out of — composition over configuration, small focused helpers over god-functions, duplication over a flag that encodes a guess. The deep signal is that the cost asymmetry runs the other way from intuition: undoing duplication is a mechanical merge; undoing a wrong abstraction means untangling everything that came to depend on it. So when unsure, wait. Let the divergence show itself, extract on the third occurrence, and pick abstractions you could delete without a rescue mission. The presentational-vs-container exercise is a good example of an abstraction that is worth it (a stable, real boundary), and combine-reducers shows abstracting by clean slices rather than by premature flags — both cases where the shape was known before the extraction, which is exactly the condition that makes an abstraction pay.