The Dispatch on Abstraction

DRY Is Overrated

Duplication isn't the enemy — premature abstraction is.

Don't DRY code. DRY knowledge.

DRY works for shared rules and invariants. It backfires when applied to code that just looks alike.

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

function saveOrder(order) {
  validate(order);
  db.orders.insert(order);
}
Exhibit B 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
}
Begin reading

What DRY Actually Means

DRY comes from The Pragmatic Programmer (Hunt & Thomas, 1999). Most developers learn the slogan — Don't Repeat Yourself — and apply it to any code that looks alike. The original definition is sharper:

"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."

Notice the word: knowledge , not code. That single word is the whole point. Two functions that look identical on screen can represent two completely different ideas that just happen to share a shape today — and that shape can drift apart tomorrow.

If you remember nothing else: DRY is about deduplicating ideas, not characters.

How DRY Gets Misapplied

Premature Merging

Code that looks similar often isn't the same idea. Joining two snippets that share a shape today forces them to evolve as one — even when they shouldn't.

Tight Coupling

Shared helpers create invisible dependencies. Change one thing, break three others. That's how plain code turns into a flag‑driven maze.

Indirection

Every abstraction puts a layer between you and the actual behavior. Stack a few and even simple code becomes impossible to trace.

The reality: it is easier to fix duplication later than to dismantle a complex, wrong 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

The Difference, Concretely

The same instinct — "I should extract this" — leads to very different outcomes depending on whether the shared thing is knowledge or just characters. Two examples.

When "DRYing" hurts

Before three small, obvious validators
function validateEmail(value) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

function validatePassword(value) {
  return value.length >= 8;
}

function validateAge(value) {
  return Number.isInteger(value) && value >= 18;
}
After one "universal" validator
const RULES = {
  email: { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
  password: { minLength: 8 },
  age: { kind: 'int', min: 18 },
};

function validate(value, field) {
  const r = RULES[field];
  if (r.pattern) return r.pattern.test(value);
  if (r.minLength) return value.length >= r.minLength;
  // every new rule kind adds another branch...
}

Each validator is its own idea — what makes an email valid is unrelated to what makes an age valid. They share characters, not knowledge.

When DRY is exactly right

Before one tax rate, copied everywhere
function invoiceTotal(item) {
  return item.price * 1.21;
}

function cartTotal(item) {
  return item.price * 1.21;
}

function receiptTotal(item) {
  return item.price * 1.21;
}
After one source of truth
const VAT_RATE = 0.21;

function withTax(price) {
  return price * (1 + VAT_RATE);
}

// invoiceTotal, cartTotal, receiptTotal all call withTax()

The VAT rate is one piece of knowledge — "how much tax we charge" — that the whole system shares. When the rate changes, it must change everywhere at once. That's the case DRY was invented for.

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.

Warning Signs

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 that two ideas got glued together.

Common DRY Traps

These have names. Once you've seen them, you'll start spotting them in every codebase you touch.

The God Helper

A "shared utility" that grows into 2,000 lines of unrelated functions glued together by being kind of useful. Every team adds to it. Nobody owns it. Nobody dares change it.

The Flag-Driven Switch

A function that started with one parameter and now takes six booleans. Each was added to support "just one more case." Reading a callsite means re-reading the function to figure out what the flags do.

Config-Object Hell

A function that accepts a 20-field config object — half the fields only matter if another field is set. The body is mostly defaulting and branching. It's a flag-driven switch in a tuxedo.

The Inheritance Pyramid

A six-level class hierarchy where understanding one method means opening four files. The "shared" behavior is so far up the tree that nobody knows which subclasses still need it.

All four come from the same instinct: "I see duplication, I must remove it."

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.

Common Worries

Questions that come up when you start pushing back on "DRY this":

"But isn't copy-pasting just lazy?"

Copy-pasting without thinking is lazy. Copy-pasting after asking "do these change for the same reason?" is a deliberate choice. The opposite — extracting everything that looks alike — is also lazy thinking, just disguised as work.

"My senior keeps telling me to DRY this up."

Ask the diagnostic out loud: "Do these change for the same reason?" If yes, they're right — abstract. If no, ask what knowledge the duplication actually represents. Most "DRY this" feedback survives that question. Some doesn't, and that's the conversation worth having.

"How do I know when two things are really the same?"

Wait. The Rule of Three says: don't abstract until you've seen the same idea show up at least three times — preferably in three different contexts. Two examples can fool you. Three usually can't.

"I already wrote the wrong abstraction. Now what?"

Inline it. Copy the body back into each callsite, then look at what each one actually needs. Once the code is honest about being different, the right shared parts — if any — become obvious.

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.