DRY, KISS, and YAGNI
Three rules of thumb that keep the principles honest — one source of truth, the simplest thing that works, and nothing you don't need yet.
Big idea
- DRY — Don't Repeat Yourself: every piece of knowledge should have one authoritative home.
- KISS — Keep It Simple: prefer the simplest design that solves today's problem clearly.
- YAGNI — You Aren't Gonna Need It: don't build for requirements you only imagine.
They pull against SOLID and patterns on purpose: principles tell you how to make code flexible; these tell you when flexibility isn't worth its cost.
The problems they solve
- Without DRY, the VAT rate is
0.2in the cart, the invoice, and the refund code. It changes; two get updated; refunds are now wrong. - Without KISS, a three-field settings form is built on a plugin system with an event bus because "it might grow".
- Without YAGNI, a team spends a sprint on multi-currency support for a product that only ever sells in one country — and then has to maintain it forever.
Think of it like
Packing for a weekend trip. DRY: one list, not three slightly different lists on three scraps of paper. KISS: a backpack, not a modular luggage system. YAGNI: don't pack the snow boots for the beach "in case". You can buy boots if it actually snows.
DRY is about knowledge, not text
Two lines that look the same aren't necessarily duplication. If they'd change for different reasons, merging them couples two unrelated rules — the bug from Single Responsibility. Ask: if this changes, must the other change too? Only then are they the same knowledge.
A useful guard: the rule of three — tolerate a second copy; extract on the third, when you can see what's genuinely shared.
Watch it
Hands on
The shop below repeats its discount knowledge. Someone raised the member discount to 15% in one place and missed the other, so the receipt and the charged amount disagree. Make the rule live in one place so both lines print the same correct price.
def cart_total(prices, member):
total = sum(prices)
if member:
total *= 0.85 # updated to 15% off
return round(total, 2)
def receipt_line(prices, member):
total = sum(prices)
if member:
total *= 0.90 # still the old 10% — nobody remembered this copy
return f"You pay {round(total, 2):.2f}"
prices = [20.0, 30.0]
print(f"charged {cart_total(prices, True):.2f}")
print(receipt_line(prices, True))output
charged 42.50
You pay 42.50Run and edit it in the app.
KISS and YAGNI in practice
- Write the straightforward version first; let real change requests show you where flexibility is needed.
- A pattern is a cost you pay for a specific flexibility. If you can't name the change it enables, skip it.
- Leave code easy to change — small functions, tests, clear names — instead of pre-building the change. That's the cheap insurance.
- YAGNI is about features and abstractions, not quality. Tests, error handling, and security aren't speculative.
validate_username() and validate_tag() both check len(x) <= 20 today. Product says usernames may become 30 soon; tags won't change. Should you merge them into one function?
Which is the clearest YAGNI violation?
Pros and cons
| Pros | Cons when misapplied | |
|---|---|---|
| DRY | One place to change a rule | Over-merging couples unrelated rules |
| KISS | Code anyone can read | "Simple" can become "naive" for real complexity |
| YAGNI | No time spent on unused features | Ignoring a known, imminent need costs a rewrite |
How it relates
- They're the brake on Open/Closed and every pattern in the GoF families — use a pattern when the variation is real.
- The smells Duplicate Code and Speculative Generality are DRY and YAGNI violations.
- Refactoring is what makes YAGNI safe: you can add the flexibility later, in small steps.
Key terms
- DRY — Each piece of knowledge has a single, authoritative representation.
- KISS — Prefer the simplest design that clearly solves the problem.
- YAGNI — Don't build functionality until it's actually needed.
- Rule of three — Tolerate duplication once; extract the shared part on the third occurrence.