Don't Repeat Yourself
Every fact has one home. The second copy is the bug.
Hunt and Thomas wrote it down in 1999 and the wording has not aged: every piece of knowledge must have a single, unambiguous, authoritative representation within a system. Two copies of the same fact mean the next maintainer changes one and the bug ships from the unchanged second. The regression resists git-blame because the lie was committed twice.
Opinion
I've watched DRY get applied as a reflex more than as a rule. Pragmatic Programmer readers extract a helper the moment they see two similar lines; six months later the helper carries flags for every caller and a name that no longer covers any of them. The fix is not to abandon DRY. The fix is to read the second sentence Hunt and Thomas wrote and notice the word knowledge.
Couple this with F4 Simplicity: extract on the second occurrence, never extract across a bounded context. Inside a context, deduplication is cheap insurance. Across one, the helper fuses two products into a single change.
Copy a note and link
Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.
Each fact in the code should live in one place. If the same logic or value shows up two or three times, pull it into a single helper or constant. But don't merge code just because it looks the same — only merge if it means the same thing. Code that happens to look alike but represents different ideas should stay separate. /tenet/dont-repeat-yourself/F3
AI eyes only
Rule: every fact has one home. Duplicate intent is debt.
Reject: copy-paste with one-line edits. Reject: near-duplicate helpers across modules. Reject: ad-hoc reformulations of utilities that already exist in lib/generic/ or lib/product/.
Generate: search the codebase for an existing helper before writing one. Reuse the canonical version. If no canonical version exists, extract on the second occurrence: agent diff cost has collapsed and waiting compounds the duplication.
Diagnostic: before emitting a function or constant, grep for the body. If a near-match exists, reuse it. Repeated literals are the same trigger as repeated logic.
Why?
- One home for each fact: the next change is one diff, not a treasure hunt for siblings.
- Bugs that come from forgetting to update the second copy disappear by construction — the second copy isn't there.
- Reviewers see a small change to the canonical helper, not seven scattered edits in unrelated files.
- Forces a name on the duplicated knowledge — feeds F2 Intention-Revealing Names by making the noun explicit.
- Repetition is often a missing unit. Extracting reveals a single-purpose function or type that F1 would have demanded anyway.
- One canonical implementation is one test surface — fewer redundant tests proving the same rule from six angles.
- Renames, units changes, validation tweaks become cheap. The cost of correctness drops when correctness has one home.
Origins
Hunt & Thomas coined the acronym in The Pragmatic Programmer (1999), tip 11: “every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” The 20th-anniversary edition (2019) expands the wording but keeps the rule intact: DRY is about knowledge duplication, not text duplication.1The Pragmatic Programmer, 20th Anniversary Edition (Addison-Wesley, 2019). Tip 11 — “DRY: Don’t Repeat Yourself.” The original 1999 wording is preserved.
Kent Beck arrived at the same idea by a different route. The four rules of simple design from Extreme Programming Explained (1999), popularised by Martin Fowler, end with “no duplication” as the second strongest constraint after passing the tests.2Four rules of simple design — popularised by Martin Fowler at martinfowler.com/bliki/BeckDesignRules.html. Beck’s ordering puts no duplication third, after tests passing and intent revealed.
Robert C. Martin folds DRY into Clean Code (2008) as the structural counterpart to meaningful names: duplication is dishonest because it lies about which copy is the truth.3Clean Code (Prentice Hall, 2008). Treats duplication as the principal cause of bad code; ties DRY to SRP via the missing-unit observation.
The most influential modern critique is Sandi Metz' 2014 RailsConf talk All the Little Things and her 2016 essay The Wrong Abstraction. The line “duplication is far cheaper than the wrong abstraction” reframes DRY as a tool, not a doctrine. Kent C. Dodds packages the corollary as AHA Programming: avoid hasty abstractions; prefer duplication until the third callsite shows the shape.4The Wrong Abstraction (sandimetz.com, 2016). Companion talk: All the Little Things (RailsConf 2014). Kent C. Dodds frames the corollary as AHA Programming.
Eric Evans draws the boundary in Domain-Driven Design (2003): across a bounded context, deduplication is harmful. Two contexts will diverge; fusing them creates a coupling that costs more than the duplication it deleted.5Domain-Driven Design (Addison-Wesley, 2003). Bounded contexts are the seam at which DRY stops applying.
Quotes
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Duplication is far cheaper than the wrong abstraction. Prefer duplication over the wrong abstraction.
Passes the tests · Reveals intention · No duplication · Fewest elements. In that order. Duplication ranks third because it is the easiest of the four to mistake for the others.
Avoid Hasty Abstractions. Prefer duplication over the wrong abstraction and optimise for change first.
Evidence
Twenty external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.
- 01The canonical statement of the rule. The 20e adds the gloss that DRY is about knowledge, not lines.
- 02Mechanical recipes for collapsing duplication once it is real. Pair with Inline when the abstraction was wrong.
- 03Frames duplication as the root of bad code; ties DRY to SRP because the duplicated logic almost always reveals a missing unit.
- 04Four rules of simple design — no duplication is third, after tests pass and intent is revealed.
- 05The Wrong AbstractionQualifiesThe most cited counter to DRY puritanism. The right move when an abstraction is wrong is often to inline it back to duplication and start again.
Sixteen sources, four stances, one rule. The supporters trace from Hunt and Thomas in 1999 through Martin and Beck; Fowler catalogues the recipes. The qualifiers led by Metz install the brakes: respect the context boundary, distinguish coincidence from knowledge. The rule-of-three timing rule is named and refused below. The opposing “WET” line beneath the canon restates the qualifiers' case in stronger words. The rule survives the room.
Examples
// Before: 0.20 lives in three places. One drift becomes a bug.function priceWithVat(net: number): number { return net * 1.20;}function vatAmount(net: number): number { return net * 0.2;}function isReducedRate(rate: number): boolean { return rate < 0.20;}
// After: VAT_RATE is the single source of truth.const VAT_RATE = 0.20;function priceWithVat(net: number): number { return net * (1 + VAT_RATE);}function vatAmount(net: number): number { return net * VAT_RATE;}function isReducedRate(rate: number): boolean { return rate < VAT_RATE;}
Enforcement
Apply these rules in .jscpd.json + eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.
| Rule | Tool | Catches |
|---|---|---|
| jscpd | jscpd | copy-pasted blocks of 5+ lines / 50+ tokens across files — the duplication review forgets to flag. |
| sonarjs/no-identical-functions | eslint-plugin-sonarjs | two functions with the same body and a different name — usually a missing helper or a missed rename. |
| sonarjs/no-duplicate-string | eslint-plugin-sonarjs | the same literal in three or more places — the constant you forgot to extract. |
.jscpd.json + eslint.config.mjsconfiguration snippet
// jscpd.config.json — copy/paste detection in CI
{
"threshold": 0,
"min-tokens": 50,
"min-lines": 5,
"format": ["typescript", "tsx", "javascript", "jsx"],
"ignore": ["**/*.spec.ts", "**/*.test.tsx", "**/__fixtures__/**"],
"reporters": ["console", "html"],
"absolute": true
}
// eslint.config.mjs — flag the same expression appearing twice
import sonarjs from 'eslint-plugin-sonarjs';
export default [
sonarjs.configs.recommended,
{
rules: {
'sonarjs/no-identical-functions': ['error', 3],
'sonarjs/no-duplicate-string': ['error', { threshold: 3 }],
'sonarjs/no-identical-expressions': 'error',
'sonarjs/no-redundant-jump': 'error',
},
},
];AI rules
.cursor/rules/f3-dont-repeat-yourself.mdc---
description: Prickles F3 — Don't Repeat Yourself
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---
## Prickles F3 — Don't Repeat Yourself
Every fact, rule, and decision in the system has one home. If you find yourself typing the same logic, the same literal, or the same shape twice, the second copy is a bug looking for a deadline.
Extract when the duplication represents the same knowledge. Leave it alone when the shapes look alike but the reasons differ — coincidental duplication is not duplication.
Constants over magic literals. A string that means the same thing in three places is a constant; a string that happens to spell the same letters in three places is a coincidence.
Cross a bounded context and DRY stops applying — duplicate the type, copy the helper, leave the seam visible. See F1 Single Responsibility for the unit boundary, A8 Bounded Contexts for the system boundary.
Refuse premature abstraction. Wait for the third occurrence (Rule of Three) before generalising; the wrong abstraction is more expensive than the duplication it replaced.Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.
Counter-argument
The first steelman is Sandi Metz' The Wrong Abstraction: the cost of unwinding a bad abstraction is higher than the cost of the duplication it replaced, because the abstraction develops branches, flags, and second-meaning callsites that fuse unrelated decisions. Add bounded contexts and the rule appears to flip. Two services that share a User shape today will diverge tomorrow, and the “shared type” becomes a coupling neither team can refactor without scheduling a meeting.
The second steelman is the classic rule of three. Fowler attributes it to Don Roberts; Beck restated it in XP Explained as “three strikes and you refactor.” Extract too early and the abstraction is wrong; wait until the third caller lands and the shape of the helper is obvious.
Counter-argument retort
The first steelman (Metz) is true, and it is not an argument against DRY: it is the second sentence of Hunt and Thomas' original tip, which most quotes drop. Knowledge, not text. If two callsites encode the same rule, collapse them; if they happen to share a body for now, leave them and rename the helper when the third caller reshapes the abstraction.
For bounded contexts the answer is also in the canon: cross a context, duplicate the type. The cost of the duplication is paid once and it is bounded; the cost of the coupling compounds for the life of the system. A8 carries that load deliberately so F3 does not have to.
The rule-of-three steelman is acknowledged and refused. Don't wait for three. The Fowler/Beck argument is an artefact of an era when the cost of detecting and renaming a helper was high. With AI agents reading the diff before the human does, that cost has collapsed. Waiting for the third caller is now waiting while the duplication compounds, the helper grows three subtly different copies, and the bug ships in two of them. Extract on the second occurrence; if the third caller reshapes it, the agent renames the helper in seconds. The wrong abstraction is still cheap to fix; the duplication that was missed is not.
Default stance for code inside a single context: extract on the second occurrence, refuse the magic literal the moment it repeats, accept that the right number of times to write a rule is one. Everything else is an exception that gets defended in the PR description.
Notes
- [1]Hunt & Thomas — The Pragmatic Programmer, 20th Anniversary Edition (Addison-Wesley, 2019). Tip 11 — “DRY: Don’t Repeat Yourself.” The original 1999 wording is preserved.
- [2]Kent Beck — Four rules of simple design — popularised by Martin Fowler at martinfowler.com/bliki/BeckDesignRules.html. Beck’s ordering puts no duplication third, after tests passing and intent revealed.
- [3]Robert C. Martin — Clean Code (Prentice Hall, 2008). Treats duplication as the principal cause of bad code; ties DRY to SRP via the missing-unit observation.