The principle that trips up even experienced engineers

DRY Is Overrated

Duplication isn't the enemy — premature abstraction is.

Don't DRY code. DRY knowledge.

DRY is great for shared rules and invariants — it's misused when applied to similar‑looking code.

✅ Before: Simple & Clear
function saveUser(user) {
  validate(user);
  db.users.insert(user);
}

function saveOrder(order) {
  validate(order);
  db.orders.insert(order);
}
❌ After: "DRY" Refactor
// Looks reasonable at first...
function saveEntity(entity, type) {
  validate(entity);
  db[type].insert(entity);
}

// 6 months later...
function saveEntity(entity, opts) {
  const cfg = CONFIG[opts.type];
  if (opts.validate !== false) {
    (cfg.validator || validate)(entity);
  }
  // ... 40 more lines
}

How DRY Gets Misapplied

Premature Merging

Code that looks similar isn't always conceptually related. Merging unrelated code couples things that should evolve independently.

Tight Coupling

Shared helpers create invisible dependencies. Change one thing, break three others. This is how simple code turns into a flag‑driven maze.

Indirection

Every abstraction adds a layer of cognitive load. Too many layers make code impossible to trace for newcomers.

The Reality: Duplication is cheaper than the wrong abstraction. It is easier to fix duplication later than to dismantle a complex, incorrect abstraction.

Visual Similarity ≠ Conceptual Similarity

Code that looks the same is not necessarily the same thing.

Do these pieces change for the same reason?
If yes →

Abstraction may be justified

If no →

Abstraction creates coupling debt

Duplication vs Wrong Abstraction

Duplication Costs

  • A few extra lines
  • Maybe an extra test
  • A possible refactor later

Wrong Abstraction Costs

  • Cognitive overhead forever
  • Fear-driven changes
  • Hidden dependencies
  • "Action at a distance" bugs

Duplication is cheaper than the wrong abstraction.

A Practical Smell Test

Be cautious when an abstraction:

Requires boolean flags or "mode" parameters

Uses conditionals to support multiple cases

Can't be named without "and/or/depending on"

Breaks unrelated behavior when changed

These are signs of merged concepts.

Know When to Duplicate vs Abstract

When Duplication Is Right

  • Behaviors are likely to diverge
  • Readability is more important than reuse
  • Extracting would introduce flags
  • You're still learning the problem space

Clarity today beats hypothetical reuse tomorrow.

When to Abstract Safely

  • The same rule is repeated
  • Changes must happen together
  • The shared concept has a clear name
  • No branching or mode-switching is required

Good abstraction removes complexity — it doesn't relocate it.

Better Heuristics

AHA

Avoid Hasty Abstractions. Wait until you fully understand the pattern before you abstract.

WET

Write Everything Twice. It's okay to copy-paste. Abstract only on the third time (or fourth).

YAGNI

You Aren't Gonna Need It. Don't build for hypothetical futures. Build for today.

Same Reason to Change

Only extract when changes should happen together. If two pieces of code change for different reasons, keep them separate.

A Useful Compromise Pattern

Instead of one "do-everything" function:

✓ Do This

  • Keep separate, explicit functions
  • Share only the smallest true helper
  • Prefer composition over configuration
  • Use strategy maps instead of conditionals

✗ Avoid This

  • One giant function with many parameters
  • Boolean flags that change behavior
  • Deep if/else or switch statements
  • "Universal" abstractions that do everything

This preserves clarity without duplication of knowledge.

How to Use This in PR Reviews

Instead of saying:

"This should be DRY."

Try asking:

  • "Do these change for the same reason?"
  • "Does this abstraction reduce or increase complexity?"
  • "What happens when one case evolves independently?"

This reframes the discussion away from dogma.