DRY: Don't Repeat Yourself

Every piece of knowledge should live in one place. Learn when to remove duplication, and when duplication is actually fine.

Beginner⏱ 3 min readLesson 7 of 16#clean-code#dry#principles#duplication

The big idea

Imagine your phone number is written on 12 sticky notes around the house. You change your number. How many notes will you forget to update?

DRY: one source of truth instead of many copies that drift apartDRY: one source of truth instead of many copies that drift apart

DRY = Don't Repeat Yourself. From The Pragmatic Programmer:

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

Note the word knowledge. DRY is about rules and facts, not about lines of text that happen to look the same.

The copy-paste bug

Drawing diagram…
// ❌ The same rule in three places
// signup.js
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) throw new Error("Invalid email");
// profile.js
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) throw new Error("Invalid email");
// admin.js
if (!/^[^@\s]+@[^@\s]+$/.test(email)) throw new Error("Bad email"); // already drifted!
// ✅ One source of truth
// validation/email.js
const EMAIL_PATTERN = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
export const isValidEmail = (email) => EMAIL_PATTERN.test(email);

// everywhere else
import { isValidEmail } from "./validation/email.js";
if (!isValidEmail(email)) throw new ValidationError("Invalid email");

DRY is more than functions

Duplicated knowledgeDRY fix
Magic numbers (0.2 tax everywhere)A named constant: TAX_RATE
The same validation in frontend and backendA shared schema (for example, Zod) used by both
API types written by hand in the clientGenerate types from OpenAPI / GraphQL schema
Config values copied between filesOne .env / config module
The same SQL in many placesA repository function
Repeated UI markupA reusable component
Drawing diagram…
// schemas/user.ts — used by the React form AND the API route
import { z } from "zod";

export const userSchema = z.object({
  email: z.string().email(),
  age: z.number().int().min(18),
});
export type User = z.infer<typeof userSchema>;

⚠️ The trap: wrong abstraction

Two pieces of code can look the same today but represent different knowledge. Merging them couples things that should change independently.

// These look identical…
const validateProductName = (name) => name.length > 0 && name.length <= 50;
const validateUsername = (name) => name.length > 0 && name.length <= 50;

Should you merge them into validateName()? No. Product names and usernames are different business rules. Next month usernames will need "no spaces" and product names will allow 100 characters. If merged, you will add flags:

// ❌ The wrong abstraction grows ugly
function validateName(name, isUser, allowLong, allowSpaces) { /* … */ }

💡 "Duplication is far cheaper than the wrong abstraction." (Sandi Metz)

The Rule of Three

A practical guide for when to extract shared code:

Drawing diagram…

By the third time you can see what is truly common and what varies.

DRY vs WET

  • DRY: Don't Repeat Yourself.
  • WET: "Write Everything Twice" or "We Enjoy Typing". Copy-paste culture.
  • AHA: "Avoid Hasty Abstractions". The balanced middle ground: prefer duplication until the right abstraction is obvious.

Key takeaways

  • DRY is about knowledge, not identical-looking text.
  • Business rules, constants, schemas and config should each live in one place.
  • Don't merge code that only looks alike but changes for different reasons.
  • Use the Rule of Three before extracting.

Try it yourself

Search your project for a number like 0.2, 100, or 86400. Is it repeated? Give it a name and a single home.