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?
Abstraction may be justified
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
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;
} 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
function invoiceTotal(item) {
return item.price * 1.21;
}
function cartTotal(item) {
return item.price * 1.21;
}
function receiptTotal(item) {
return item.price * 1.21;
} 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.
Resources
These talks and essays explore the cost of unnecessary abstraction and hidden complexity.