Skip to main content

DRY - Don't Repeat Yourself

Every piece of knowledge should have one authoritative place to live in a system.

Not one copy of the code - one copy of the fact. A free-shipping threshold hardcoded as 50 in the checkout summary and again in the invoice PDF generator is not two pieces of logic; it is one fact that forgot to pick a home.

Why it exists

The day that fact changes, the cost of having two homes for it comes due. Marketing decides free shipping now kicks in at $75. Someone updates the checkout summary, ships it, and moves on. Three weeks later a support ticket comes in: a customer paid shipping on a $60 order that the invoice PDF - still running on the old 50 - insists should have been free. Nobody lied to the customer on purpose. The fact just had two addresses, and only one got the memo.

Multiply that by twelve places instead of two and you no longer have a bug, you have an archaeology project: finding every buried copy of a fact before you can trust that changing it changed anything.

A duplicated fact, and its fix

// Two methods, two silent copies of the same business rule.
method renderCheckoutSummary(order) is
shipping = order.subtotal >= 50 ? 0 : 6.99
print("Shipping: " + shipping)
 
method renderInvoice(order) is
shipping = order.subtotal >= 50 ? 0 : 6.99
print("Shipping charged: " + shipping)
 
// Marketing raises the free-shipping threshold to 75.
// Someone updates renderCheckoutSummary. Nobody remembers
// renderInvoice exists, because nothing points them to it.

Try it yourself: before revealing the fix above, write down what you would extract if a third call site - a marketing banner that says "Free shipping over $50!" - also needed to know the threshold. Does your answer still work if the threshold someday needs to vary by country?

The cost of overusing it

DRY has a well-known failure mode: applying it to code that merely looks alike instead of code that means the same thing. Two five-line validation functions - one checking a zip code, one checking a product SKU - can be byte-for-byte identical today and still be coincidentally identical, not duplicated. Merge them into one validateFormat() and the day the SKU rule needs a checksum digit, you are untangling a shared function that never should have been shared, adding a type parameter and a branch to serve two callers that had nothing to do with each other.

Repetition is cheap to fix later. The wrong abstraction, once three call sites depend on it, is not. When in doubt about whether two blocks share a fact or just share a shape, wait for a third occurrence before extracting - two is a coincidence you can still afford.

How it relates to its neighbours

DRY and Encapsulate What Varies sound similar and are not the same axis. DRY is about sameness: one fact, one home, regardless of whether that fact ever changes. Encapsulate What Varies is about change: walling off a part of a class specifically because it changes on a different schedule than the rest, even if that part appears exactly once. A constant that never varies can still violate DRY if it is copy-pasted; a piece of code that appears exactly once can still need encapsulating if it is volatile.

Where you'll see it in the pattern catalog

Template Method exists largely to eliminate DRY violations in algorithm skeletons - two subclasses that each reimplement "open, read, process, close" with only the process step actually differing. Strategy solves the same duplication from the composition side. Anywhere you see near-identical method bodies in sibling classes, one of these two patterns is usually the DRY fix waiting to happen.

Check yourself

Question 1 of 3

A free-shipping threshold of $50 is hardcoded in both the checkout summary and the invoice generator. What did DRY actually get violated by?