The Hidden Cost of DRY: When Duplication is the Better Path

The Hidden Cost of DRY: When Duplication is the Better Path

The Hidden Cost of DRY: When Duplication is the Better Path

Your no-BS weekly brief on software engineering.
Join 100,000+ developers

Every developer is taught to "Keep your code DRY." At first, it sounds like a universal truth—duplication is bad, abstractions are good, and cleaner code is always better. But as you gain experience, you start to notice something: blindly following DRY can make your code harder to work with, not easier.

The problem isn’t DRY itself—it’s assuming that every duplication must be eliminated immediately. But context matters. Sandi Metz puts it best: "Every rule can be broken if your pair agrees." That includes DRY.

The real danger is premature abstraction—optimizing before you actually need to. Instead of making your code more maintainable, it often makes change painful. The question isn’t “How do I remove duplication?”—it’s “Is abstraction actually helping me here, or making things worse?”


Premature Abstraction: The Cost of Over-Optimizing

Developers love elegant solutions. The temptation to create reusable, abstracted code is strong. But when we extract logic too soon, we often end up with something that:

  • Forces together concepts that aren’t actually the same thing.
  • Creates dependencies that make small changes unexpectedly difficult.
  • Requires constant modification as requirements evolve.

Ironically, in trying to prevent duplication, we introduce rigidity.

Instead of solving the problem at hand, we future-proof for scenarios we think might happen. The problem? We will always know more tomorrow than we do today.

So if we don’t need to make a decision today—don’t.


A Real-World Example: Designing for Adaptability

It’s one thing to talk about principles like KISS and YAGNI, but how does this play out when designing real systems?

We often convince ourselves that future-proofing is always the right move—but sometimes, flexibility is more valuable than abstraction.

I learned this firsthand when working on a critical feature for a client. We knew the feature would evolve, but we didn’t yet know how. Instead of trying to force a single, all-encompassing design, we spent more than a week architecting a database model that would allow for emerging complexity, rather than assuming we could predict every future requirement.

The key wasn’t just avoiding duplication—it was designing for adaptability without locking ourselves into rigid abstractions too early. Had we forced an abstraction too soon, we might have built something too specific, making future changes harder rather than easier.

This is the fine line we walk in software design. DRY isn’t just about removing duplication—it’s about creating a structure that remains flexible as the codebase evolves.

KISS, YAGNI, and Waiting for the Right Moment

This thinking goes hand-in-hand with other software design principles:

  • KISS (Keep It Simple, Stupid): The simplest solution that works is usually the best.
  • YAGNI (You Ain’t Gonna Need It): Don’t build things unless you’re sure they’re necessary.
  • Encapsulation Over Abstraction: Code that’s easy to delete or replace is more valuable than code that’s universally reusable.

It’s far easier to refactor well-structured duplication than it is to untangle a premature abstraction. Removing duplication when necessary is simple; reworking an over-engineered abstraction is painful.

So instead of asking, "How can I avoid duplication?" ask:

"Do I even need to extract this yet?"

If the answer isn’t a clear yes, leave it alone.

Don't fit a square peg into a round hole.


DRY Done Right: When to Extract, When to Duplicate

So how do you know when to apply DRY and when to hold off? A few guiding questions:

Extract the Code (Apply DRY) When:

✅ The duplication represents a clear, stable concept. ✅ The logic is unlikely to change independently in different places.
✅ The abstraction simplifies the code rather than making it harder to follow.
✅ The need for reuse is already present, not just hypothetical.

Keep the Duplication When:

🔸 The code may look the same but serves different purposes. 🔸 The behavior is still evolving and likely to diverge over time.
🔸 The abstraction introduces more complexity than it removes.
🔸 You’re only removing duplication for the sake of removing duplication.

It’s always easier to extract common behavior when it’s clear rather than guessing before you need to.


The Art of Thoughtful Abstraction

DRY is a powerful principle, but blindly following it can lead to code that's harder to change, not easier. The best developers aren’t the ones who apply DRY everywhere—they’re the ones who know when to leave duplication alone.

Most abstractions fail not because they're wrong, but because they happen too soon. If we don't have enough information, we end up making assumptions that won’t hold up tomorrow.

So don’t rush it.

  • Keep things simple.
  • Don’t optimize prematurely.
  • Refactor when the patterns are obvious, not before.

Because in the end, the best code isn’t just reusable—it’s adaptable.