DRY — Don’t Repeat Yourself

Purpose, history, misconceptions & related topics

August 12, 2025

DRY Principle — Demo

Agenda

  1. What DRY actually means
  2. Why it matters
  3. A (very) short history
  4. Misconceptions & pitfalls
  5. Concrete examples
  6. Related ideas & heuristics
  7. Checklist to take away
DRY Principle — Demo

Definition

“Every piece of knowledge should have a single, unambiguous, authoritative representation in the system.”

  • Single source of truth for knowledge
  • It’s about knowledge duplication, not just code duplication
DRY Principle — Demo

Why DRY?

  • Lowers change cost (one edit, everywhere updated)
  • Reduces inconsistencies & bugs
  • Improves readability and onboarding
  • Enables reuse → faster delivery
DRY Principle — Demo

Tiny History

  • Coined/popularized in The Pragmatic Programmer (1999)
  • Rooted in older ideas: database normalization, SSOT, reuse
  • Sister terms: WET (“We Enjoy Typing”) as the opposite
DRY Principle — Demo

What Counts as Duplication?

  • Business rules copied across modules/services
  • Constants/config sprinkled everywhere
  • UI patterns re-implemented per component
  • SQL snippets cloned across queries
  • Docs that drift from code
DRY Principle — Demo

Example 1 — Business Rule (Before)

type Money = number;

const loyaltyDiscountRate = 0.1;

export function finalPriceForCheckout(total: Money, isLoyal: boolean): Money {
  const discount = isLoyal ? total * loyaltyDiscountRate : 0;
  const taxed = (total - discount) * 1.19;
  return Math.round(taxed * 100) / 100;
}

export function finalPriceForQuote(total: Money, userTier: "guest" | "loyal"): Money {
  const discount = userTier === "loyal" ? total * loyaltyDiscountRate : 0;
  const taxed = (total - discount) * 1.19;
  return Math.round(taxed * 100) / 100;
}
DRY Principle — Demo

Example 1 — Business Rule (After)

type Money = number;

type TaxPolicy = { rate: number };
type DiscountPolicy = (total: Money, context: { isLoyal: boolean }) => Money;

const vatDE: TaxPolicy = { rate: 0.19 };

const loyaltyDiscount: DiscountPolicy = (total, { isLoyal }) =>
  isLoyal ? total * 0.1 : 0;

function priceWithPolicies(
  total: Money,
  context: { isLoyal: boolean },
  discount: DiscountPolicy,
  tax: TaxPolicy
): Money {
  const discounted = total - discount(total, context);
  const taxed = discounted * (1 + tax.rate);
  return Math.round(taxed * 100) / 100;
}

export const finalPriceForCheckout = (total: Money, isLoyal: boolean) =>
  priceWithPolicies(total, { isLoyal }, loyaltyDiscount, vatDE);

export const finalPriceForQuote = (total: Money, isLoyal: boolean) =>
  priceWithPolicies(total, { isLoyal }, loyaltyDiscount, vatDE);
  • Single place encodes business knowledge
DRY Principle — Demo

Example 2 — Validation Shared (FE + BE)

// schema.ts (shared package)
import { z } from "zod";

export const CreateUser = z.object({
  email: z.string().email(),
  password: z.string().min(12),
  marketingOptIn: z.boolean().default(false)
});
export type CreateUser = z.infer<typeof CreateUser>;
// backend
app.post("/users", (req, res) => {
  const parsed = CreateUser.safeParse(req.body);
  if (!parsed.success) return res.status(400).json(parsed.error.format());
  // use parsed.data
});
// frontend
const form = useForm<CreateUser>({ resolver: zodResolver(CreateUser) });
  • One schema → one truth
DRY Principle — Demo

Example 3 — SQL Duplication → CTE/View

-- BEFORE: repeated customer filter everywhere
SELECT id, name FROM customers
WHERE active = true AND deleted_at IS NULL AND country = 'DE';

-- AFTER: single source
WITH active_customers AS (
  SELECT * FROM customers
  WHERE active = true AND deleted_at IS NULL
)
SELECT id, name
FROM active_customers
WHERE country = 'DE';
  • Or create a VIEW active_customers AS ...
DRY Principle — Demo

Misconceptions

  • “Never repeat a line of code.”
    DRY targets knowledge, not superficial text patterns.
  • Premature abstractions are DRY.
    No—abstractions must be validated (Rule of Three).
  • Tests should be DRY.
    Prefer readable tests; small repetition can aid clarity.
  • One mega-module is DRY.
    Centralization ≠ good design; cohesion still matters.
DRY Principle — Demo

When Not to DRY (Yet)

  • Spikes/prototypes where change is volatile
  • Local, accidental similarity (don’t force abstractions)
  • Separate domains that only look similar
  • Performance-critical hot paths where clarity wins
DRY Principle — Demo

Heuristics & Practices

  • Rule of Three: abstract after 3 real uses
  • AHA: Avoid Hasty Abstractions
  • Consolidate constants/config (env, feature flags)
  • Prefer composition over inheritance
  • Data-driven behavior (tables/config) beats copy–paste
DRY Principle — Demo

Anti-DRY Smells

  • Shotgun surgery (one change → many edits)
  • Divergent behavior across “same” rules
  • Copied bugs
  • Forked components/styles with minor tweaks
DRY Principle — Demo
  • SRP, SoC, KISS, YAGNI, AHA
  • SSOT (Single Source of Truth)
  • DB Normalization
  • Code generation / templates / generics
  • Design systems & shared UI primitives
DRY Principle — Demo

Tactics You Can Use Today

  • Extract pure functions & domain services
  • Create shared packages for schemas/types
  • Centralize routing/endpoints/client SDKs
  • Use CTEs/Views/UDFs for query reuse
  • Build a design system & utility classes
  • Add a duplication detector (e.g., CPD, Sonar)
DRY Principle — Demo

Quick Checklist

  • Can I point to the single place that defines this rule?
  • If this changes, how many files must I edit?
  • Do similar functions differ by data, not behavior → parameterize?
  • Is the abstraction proven by ≥3 call sites?
  • Are tests readable first, DRY second?
DRY Principle — Demo

Summary

  • DRY is about authoritative knowledge, not cosmetic repetition
  • Improves maintainability, correctness, and speed
  • Balance with YAGNI/AHA to avoid over-abstraction
  • Start small: schemas, policies, queries, design tokens
DRY Principle — Demo