Updesh.
Back to Writing
EngineeringDesignBest Practices

The Hidden Cost of Premature Abstraction

·7 min read

Every engineer has felt it: you're writing your third similar function and the instinct kicks in — I should abstract this. And sometimes, you're right. But more often than not, that abstraction becomes the biggest source of technical debt in the codebase.

The Seduction of DRY

Don't Repeat Yourself is one of the most well-known principles in software engineering. It's intuitive, it feels good, and it's often correct. But it's also responsible for some of the worst abstractions I've ever encountered in production codebases.

The problem isn't the principle itself — it's the timing. Abstraction built on two data points is a guess. Abstraction built on five data points is a pattern.

// Too early — we only have two cases
function processItem(type: "user" | "post") {
  if (type === "user") {
    // ...
  } else {
    // ...
  }
}

// Better — wait for the pattern to emerge
function processUser() { /* ... */ }
function processPost() { /* ... */ }

The Real Cost

When you abstract prematurely, you're not just writing code — you're writing a contract. Every abstraction is a promise to future maintainers that these two things are the same in all important ways.

When that promise turns out to be wrong (and it often does), you face a terrible choice:

  1. Stretch the abstraction — add flags, overloads, and special cases until it becomes a monster
  2. Break the abstraction — duplicate the code anyway, but now it's harder to find and reason about
  3. Redesign — the right answer, but the most expensive one

I've seen option 1 play out more times than I can count. A nice, clean function grows isSpecialCase, skipValidation, and useOldBehavior flags. What started as an abstraction to reduce complexity has become the source of it.

The Rule of Three

A heuristic I've found useful: don't abstract until you have at least three concrete examples. One is an instance, two is a coincidence, three is a pattern.

With three examples, you have enough signal to understand what varies and what stays the same. You can build an abstraction that's actually flexible where it needs to be, and rigid where it should be.

When to Abstract

Abstraction is powerful when:

  • You understand the invariants — what will never change
  • You have enough data points to know the shape of variation
  • The duplication is causing actual pain (hard to change, easy to get out of sync)
  • The abstraction makes the call site clearer, not just the implementation

Conclusion

The next time you feel the urge to abstract, pause. Ask yourself: do I have enough data points to know what this abstraction should look like in six months? If you're not sure, wait. Duplication is cheap. Wrong abstraction is expensive.

"Wrong abstraction is far more costly than duplication." — Sandi Metz

Back to all posts