Every piece of knowledge must have a single, authoritative representation.
DRY Principle: A Complete Guide with Examples in 11 Programming Languages
The DRY (Don't Repeat Yourself) principle states that every piece of knowledge or logic must have a single, unambiguous, authoritative representation within a system. It was introduced by Andy Hunt and Dave Thomas in The Pragmatic Programmer (1999) and remains one of the most foundational principles in software development. When knowledge is duplicated — in code, configuration, documentation, or database schemas — changes require updating every copy. Miss one, and you have a bug. The DRY principle eliminates this category of risk entirely by ensuring that every fact about your system lives in exactly one place.
In this comprehensive guide, we'll explore the DRY principle with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — complete with language-specific best practices, common pitfalls, and when to apply this fundamental principle.
DRY is about knowledge, not just code. The full principle from The Pragmatic Programmer reads: "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." The word "knowledge" is deliberate — duplication of logic, data, intent, or meaning all violate DRY, even when the characters on screen differ.
The naive form of DRY violation is copy-paste: you write a function to validate an email address, then copy it into three other files because it's faster than importing it. Now you have four validators. When the validation rule changes — and it will — you have to find and update all four. You will miss one. That missed copy will live in a low-traffic code path, untested, silently accepting invalid emails for months. This is the concrete cost of DRY violations: not theoretical elegance, but very real bugs.
But DRY violations are more subtle than copy-paste. Two classes that independently track a "discount percentage" for an order are a DRY violation even if neither class shares code. If the business rule about how discounts are calculated lives in two places, both must be kept in sync. A schema migration that adds a new discount type requires changes in both. Forgetting either breaks the system — and the system won't tell you it's broken until a customer is charged the wrong amount.
DRY's solution is abstraction: find the single authoritative location for each piece of knowledge, and have everything else refer to it. Functions, constants, modules, configuration, schema migrations, generated types — all of these are DRY mechanisms. The specific tool is less important than the principle: one place, one truth.
Why Follow the DRY Principle?
The DRY principle provides several compelling benefits:
Single point of change: When a business rule changes, you update exactly one place. Every caller picks up the change automatically — no search-and-replace, no missed copies
Fewer bugs: Duplicated logic is the leading source of divergence bugs — two copies start identical and drift apart as only one is updated
Easier refactoring: Code with no duplication can be restructured with confidence; there are no hidden copies of the old logic waiting to contradict the refactored version
Smaller codebase: Less code means less to read, understand, test, and maintain — a codebase that is 30% smaller due to extracted abstractions is genuinely 30% cheaper to work with
Self-documenting intent: Named abstractions (functions, constants, types) communicate intent; const TAX_RATE = 0.08 says more than 0.08 scattered across a hundred files
Improved testability: Logic that exists in one place can be tested in one place — exhaustively, with confidence that passing tests mean the logic is correct everywhere it's used
DRY vs. Related Principles
Let's compare DRY with closely related principles:
Principle
Core Statement
Scope
Primary Mechanism
Focus
DRY
Every piece of knowledge has one authoritative representation
Single Source of Truth — one authoritative data source
Data, state, configuration
Canonical data stores
Data consistency
Key Distinctions:
DRY vs. OAOO: DRY is about knowledge — any fact, rule, or intent. OAOO is about functionality — any piece of behavior or code. DRY is broader; it applies to documentation, configuration, and schema in addition to code. OAOO is more specifically code-focused. In practice, they are deeply compatible and most teams use them interchangeably.
DRY vs. SSOT: SSOT is specifically about data — having one canonical source from which all other representations are derived. DRY is about logic and knowledge. A database that is the SSOT for user records is an application of both: DRY (no duplicate user-logic) and SSOT (one canonical store). SSOT is the data-layer manifestation of DRY.
DRY vs. WET: WET is the anti-pattern DRY prevents. It describes code where logic is freely duplicated across the codebase. WET code is the natural state of a project with no DRY discipline — it emerges from time pressure, copy-paste convenience, and lack of shared abstractions.
DRY in Depth
Understanding DRY requires recognizing its three main application areas.
Knowledge Duplication Categories:
Code duplication: The most visible form. The same logic — validation, calculation, transformation — appears in multiple functions, classes, or files. Changes require updating every copy.
Data duplication: The same value — a tax rate, a base URL, a configuration threshold — is hardcoded in multiple places. When the value changes, you search for every occurrence.
Representation duplication: The same concept is expressed twice in different forms without a single source generating both. A database schema column and a TypeScript type that must always match. An API contract documented in a README and also in code, with no shared source.
DRY Application Strategies:
Extract functions: The most basic DRY move. Any block of logic used in more than one place becomes a named, reusable function. The name documents intent; the single body ensures consistency.
Named constants: Magic numbers and literal strings scattered through code are a DRY violation. A constant named MAX_LOGIN_ATTEMPTS is a single authoritative definition — change it in one place and it changes everywhere.
Type/schema generation: In typed systems, generating types from a single schema (database schema → TypeScript types via codegen, JSON Schema → class definitions) ensures the two representations never diverge.
Configuration centralization: Environment-specific values — API endpoints, feature flags, timeouts — live in one configuration file or environment variable set, not hardcoded in the functions that use them.
Template and mixin patterns: For languages with inheritance or mixins, common behavior shared across types can be extracted into a base class or mixin, not duplicated in each subtype.
When is Duplication Actually Appropriate?
DRY is a principle, not an absolute rule. Three legitimate exceptions exist:
The Rule of Three: A single duplication is often acceptable as a placeholder. When the same logic appears a third time, it's reliably a pattern worth extracting. Premature extraction for just two instances can create wrong abstractions that are harder to change than the duplication they replaced.
Accidental similarity: Two blocks of code that look similar but represent different knowledge should not be merged. Merging them creates a hidden coupling — changing one business rule forces you to split the abstraction apart again. The test: "If this logic changes for reason A, does it necessarily change in the same way for reason B?" If not, they are different knowledge.
Performance-critical inlining: In hot paths where function call overhead is measurable, duplicating a small piece of logic for performance is a conscious, documented tradeoff — not a violation of discipline.
Concept Diagram
Here is a visual representation of how DRY transforms duplicated knowledge into a single authoritative source:
Beginner-Friendly Example
Let's start with the clearest illustration of DRY: a user registration and profile update system where email validation, username validation, and password strength rules each need to be applied in multiple places. The WET version duplicates validation logic in every handler. The DRY version extracts each rule to a single authoritative location.
from __future__ import annotations
import re
from dataclasses import dataclass
from typing importOptional# ════════════════════════════════════════════════════════════════# ❌ WET VERSION — Duplicated validation logic in every function# ════════════════════════════════════════════════════════════════defregister_user_wet(email: str, username: str, password: str) -> str:
# Email validation — inline, will be copy-pasted elsewhereifnot re.fullmatch(r"[^@]+@[^@]+\.[^@]+", email):
return"Invalid email address"iflen(email) > 255:
return"Email address too long"# Username validation — inlineiflen(username) < 3orlen(username) > 30:
return"Username must be 3–30 characters"ifnot re.fullmatch(r"[a-zA-Z0-9_]+", username):
return"Username can only contain letters, numbers, and underscores"# Password validation — inlineiflen(password) < 8:
return"Password must be at least 8 characters"ifnotany(c.isupper() for c in password):
return"Password must contain at least one uppercase letter"return"OK"defupdate_profile_wet(email: str, username: str) -> str:
# ⚠️ Same email validation logic — copied from register_user_wetifnot re.fullmatch(r"[^@]+@[^@]+\.[^@]+", email):
return"Invalid email address"iflen(email) > 255:
return"Email address too long"# ⚠️ Same username validation logic — copied againiflen(username) < 3orlen(username) > 30:
return"Username must be 3–30 characters"ifnot re.fullmatch(r"[a-zA-Z0-9_]+", username):
return"Username can only contain letters, numbers, and underscores"return"OK"defreset_password_wet(password: str, confirm_password: str) -> str:
# ⚠️ Same password validation — copied a third timeiflen(password) < 8:
return"Password must be at least 8 characters"ifnotany(c.isupper() for c in password):
return"Password must contain at least one uppercase letter"if password != confirm_password:
return"Passwords do not match"return"OK"# Problem: if the email regex changes, you update 2+ places. Miss one = silent bug.# ════════════════════════════════════════════════════════════════# ✅ DRY VERSION — Single authoritative source for each rule# ════════════════════════════════════════════════════════════════@dataclass(frozen=True)classValidationResult:
valid: bool
error: Optional[str] = None @classmethoddefok(cls) -> "ValidationResult":
return cls(valid=True)
@classmethoddeffail(cls, error: str) -> "ValidationResult":
return cls(valid=False, error=error)
# ── Single authoritative validation functions ──────────────────defvalidate_email(email: str) -> ValidationResult:
"""Single source of truth for email validation rules."""ifnot re.fullmatch(r"[^@]+@[^@]+\.[^@]+", email):
return ValidationResult.fail("Invalid email address")
iflen(email) > 255:
return ValidationResult.fail("Email address too long")
return ValidationResult.ok()
defvalidate_username(username: str) -> ValidationResult:
"""Single source of truth for username rules."""iflen(username) < 3orlen(username) > 30:
return ValidationResult.fail("Username must be 3–30 characters")
ifnot re.fullmatch(r"[a-zA-Z0-9_]+", username):
return ValidationResult.fail("Username can only contain letters, numbers, and underscores")
return ValidationResult.ok()
defvalidate_password(password: str) -> ValidationResult:
"""Single source of truth for password strength rules."""iflen(password) < 8:
return ValidationResult.fail("Password must be at least 8 characters")
ifnotany(c.isupper() for c in password):
return ValidationResult.fail("Password must contain at least one uppercase letter")
return ValidationResult.ok()
# ── Handlers that REFER to the single source — never re-implement ─defregister_user(email: str, username: str, password: str) -> str:
for result in [validate_email(email), validate_username(username), validate_password(password)]:
ifnot result.valid:
return result.error
return"OK"defupdate_profile(email: str, username: str) -> str:
for result in [validate_email(email), validate_username(username)]:
ifnot result.valid:
return result.error
return"OK"defreset_password(password: str, confirm_password: str) -> str:
result = validate_password(password)
ifnot result.valid:
return result.error
if password != confirm_password:
return"Passwords do not match"return"OK"# ── Client ─────────────────────────────────────────────────────if __name__ == "__main__":
print("=== DRY Validation System ===\n")
print("Register (valid):", register_user("jane@example.com", "jane_doe", "SecurePass1"))
print("Register (bad email):", register_user("not-an-email", "jane_doe", "SecurePass1"))
print("Update profile:", update_profile("jane@example.com", "jane_doe"))
print("Reset password:", reset_password("SecurePass1", "SecurePass1"))
print("Reset mismatch:", reset_password("SecurePass1", "Different1"))
# If password rules change, only validate_password() needs updating.
Production-Ready Example
Now let's build a pricing engine — a richer, production-grade application of the DRY principle. An e-commerce platform applies tax rates, discount rules, and price formatting consistently across order summaries, invoices, cart previews, and admin reports. The WET approach scatters these calculations and constants everywhere. The DRY approach puts each piece of pricing knowledge in one authoritative location — and all consumers derive their behavior from it.
🐍 Python
from __future__ import annotations
from dataclasses import dataclass, field
from decimal import Decimal, ROUND_HALF_UP
from typing importOptionalfrom enum import Enum
# ════════════════════════════════════════════════════════════════# SINGLE SOURCE — Pricing knowledge lives here and only here# ════════════════════════════════════════════════════════════════classTaxRegion(Enum):
US_STANDARD = "us_standard"
EU_VAT = "eu_vat"
UK_VAT = "uk_vat"
TAX_EXEMPT = "tax_exempt"# ── Single authoritative tax rates ─────────────────────────────
TAX_RATES: dict[TaxRegion, Decimal] = {
TaxRegion.US_STANDARD: Decimal("0.0875"), # 8.75%
TaxRegion.EU_VAT: Decimal("0.20"), # 20%
TaxRegion.UK_VAT: Decimal("0.20"), # 20%
TaxRegion.TAX_EXEMPT: Decimal("0.00"),
}
# ── Single authoritative discount tiers ────────────────────────
DISCOUNT_TIERS: list[tuple[Decimal, Decimal]] = [
(Decimal("1000"), Decimal("0.15")), # 15% off orders over $1000
(Decimal("500"), Decimal("0.10")), # 10% off orders over $500
(Decimal("200"), Decimal("0.05")), # 5% off orders over $200
]
# ── Single authoritative rounding rule ─────────────────────────defround_currency(amount: Decimal) -> Decimal:
"""All monetary rounding in the system uses this one function."""return amount.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
# ── Single authoritative discount calculator ───────────────────defcalculate_discount(subtotal: Decimal) -> Decimal:
"""Single source of truth for volume discount logic."""for threshold, rate in DISCOUNT_TIERS:
if subtotal >= threshold:
return round_currency(subtotal * rate)
return Decimal("0.00")
# ── Single authoritative tax calculator ────────────────────────defcalculate_tax(amount_after_discount: Decimal, region: TaxRegion) -> Decimal:
"""Single source of truth for tax calculation."""
rate = TAX_RATES[region]
return round_currency(amount_after_discount * rate)
# ── Single authoritative price formatter ───────────────────────defformat_price(amount: Decimal) -> str:
"""All price display in the system uses this one formatter."""returnf"${amount:,.2f}"# ════════════════════════════════════════════════════════════════# CONSUMERS — All derive from the single source above# ════════════════════════════════════════════════════════════════@dataclassclassLineItem:
name: str
unit_price: Decimal
quantity: int @propertydefsubtotal(self) -> Decimal:
return round_currency(self.unit_price * self.quantity)
@dataclassclassPricingBreakdown:
subtotal: Decimal
discount: Decimal
tax: Decimal
total: Decimal
region: TaxRegion
defdisplay(self, title: str = "Pricing Summary") -> None:
print(f"\n ── {title} ──")
print(f" Subtotal: {format_price(self.subtotal)}") # Uses single formatterifself.discount > 0:
print(f" Discount: -{format_price(self.discount)}")
print(f" Tax ({self.region.value}): {format_price(self.tax)}")
print(f" ─────────────────────")
print(f" Total: {format_price(self.total)}")
defprice_order(items: list[LineItem], region: TaxRegion) -> PricingBreakdown:
"""Uses every single-source function — no inline calculation."""
subtotal = round_currency(sum(item.subtotal for item in items))
discount = calculate_discount(subtotal) # Single source
after_discount = subtotal - discount
tax = calculate_tax(after_discount, region) # Single source
total = after_discount + tax
return PricingBreakdown(subtotal, discount, tax, total, region)
# ── Multiple consumers — all using the same single sources ──────defgenerate_cart_preview(items: list[LineItem], region: TaxRegion) -> str:
breakdown = price_order(items, region)
# Uses format_price — the single formatter — no inline formattingreturnf"Cart: {format_price(breakdown.subtotal)} → Total: {format_price(breakdown.total)}"defgenerate_invoice_line(items: list[LineItem], region: TaxRegion) -> str:
breakdown = price_order(items, region)
tax_rate = TAX_RATES[region] * 100# Single source for rate displayreturn (
f"Invoice | Sub: {format_price(breakdown.subtotal)} | "f"Disc: {format_price(breakdown.discount)} | "f"Tax({tax_rate:.1f}%): {format_price(breakdown.tax)} | "f"Total: {format_price(breakdown.total)}"
)
# ── Client ─────────────────────────────────────────────────────if __name__ == "__main__":
items = [
LineItem("Laptop", Decimal("899.99"), 1),
LineItem("Keyboard", Decimal("149.99"), 2),
LineItem("Monitor", Decimal("399.00"), 1),
]
print("=== DRY Pricing Engine ===")
for region in [TaxRegion.US_STANDARD, TaxRegion.EU_VAT, TaxRegion.TAX_EXEMPT]:
breakdown = price_order(items, region)
breakdown.display(f"Order — {region.value}")
print("\n" + generate_cart_preview(items, TaxRegion.US_STANDARD))
print(generate_invoice_line(items, TaxRegion.EU_VAT))
# If the US tax rate changes, update TAX_RATES once.# Cart preview, invoice, order summary, and admin report all update automatically.
Real-World Use Cases
The DRY principle applies far beyond validation and pricing. Here are the most impactful areas where DRY prevents the most bugs.
Configuration and constants: Magic numbers and strings embedded across a codebase are a DRY violation. MAX_RETRY_ATTEMPTS = 3 defined once, referenced everywhere, is DRY. The same 3 copy-pasted into five retry loops, with one loop that was later updated to 5 while the others weren't, is a real production bug with a real outage.
API contracts and types: In full-stack applications, the API request/response shape is duplicated when the backend defines it in Python and the frontend defines it again in TypeScript. Code generation from a single schema (OpenAPI, GraphQL, Protobuf) eliminates this. When the schema changes, both backend and frontend types regenerate from the same source — they cannot diverge.
Database queries: An application that constructs the same SQL fragment — WHERE deleted_at IS NULL AND active = 1 — inline in a dozen query functions has duplicated business knowledge about what "active" means. A single active_users query builder or named scope is the DRY solution. When the definition of "active" changes (adding a suspended flag, for instance), one change propagates everywhere.
Business rules in multiple tiers: Discount eligibility rules that exist in the backend (for processing) and also in the frontend (for display) and also in the database (as a CHECK constraint) are triplicated. A single canonical rule — even if it must be compiled to different environments — reduces the three places that must be updated on every rule change to one authoritative source.
Error messages: User-facing error strings hardcoded in multiple places will inevitably say different things for the same error in different parts of the application. A centralized message registry ensures every consumer says exactly the same thing for the same condition.
Language-Specific Mistakes and Anti-Patterns
❌ Mistake #1: Magic Numbers Scattered Across Functions
# BAD: 8 appears in three separate places — if the minimum changes, which do you update?defregister_user(password: str) -> bool:
returnlen(password) >= 8# ← knowledgedefvalidate_api_key(key: str) -> bool:
returnlen(key) >= 8# ← different knowledge that looks identicaldefreset_password(new_password: str) -> bool:
iflen(new_password) < 8: # ← same password knowledge, duplicatedraise ValueError("Too short")
returnTrue# GOOD: Named constant — one authoritative definition
MIN_PASSWORD_LENGTH = 8# Change here → changes everywheredefvalidate_password_length(password: str) -> bool:
returnlen(password) >= MIN_PASSWORD_LENGTH
# register_user and reset_password both call validate_password_length
No — DRY is about knowledge, not just text. Two functions can have entirely different code but still violate DRY if they both independently implement the same business rule. Conversely, two functions that look similar but represent different rules should not be merged — that would create accidental coupling. The question to ask is: "If this rule changes, should both places change in the same way?" If yes, they're the same knowledge and should be unified. If no, they're different knowledge that happens to look similar.
Q2: Can DRY be over-applied?
Yes. The most common over-application is creating a "universal" abstraction from two pieces of code that happen to look similar but represent different things. When requirement A changes, you're forced to break the shared abstraction apart — leaving you worse off than if you'd never merged them. This is why the Rule of Three is a practical guide: one occurrence is fine, two is worth noting, three is reliably a pattern worth extracting.
Q3: What is the difference between DRY and abstraction?
DRY is the principle — eliminate duplicated knowledge. Abstraction is the mechanism — functions, classes, modules, constants, generated types. DRY tells you when to abstract (when the same knowledge appears in more than one place). Abstraction gives you the tools to do it. DRY without abstraction is just an intention; abstraction without DRY gives you abstractions that don't correspond to real duplication.
Q4: How does DRY relate to the Single Responsibility Principle (SRP)?
They are complementary and mutually reinforcing. SRP says each module should have exactly one reason to change. DRY says each piece of knowledge should live in exactly one place. A DRY violation (the same rule in two places) usually means one of them will change while the other doesn't — which is also an SRP violation. Applying DRY by extracting knowledge into a single named location naturally produces SRP-compliant classes and functions.
Q5: Does DRY apply to tests?
Partially. Test code is different from production code — readable, independent tests are valuable even if they share setup code. Excessive DRY in tests produces helper functions that obscure what each test is actually testing. The convention "Arrange, Act, Assert" in each test method, even at the cost of some duplication, is intentional. However, duplicated test logic (the assertion patterns, shared fixtures, setup that is clearly identical) should still be extracted into helpers. The threshold for extraction in tests is higher than in production code.
Q6: How do I identify DRY violations in a codebase I didn't write?
Search for: (1) the same regex or validation logic in multiple files, (2) the same magic number (0.08, 3, 50) appearing more than twice without a named constant, (3) the same error message string in multiple handlers, (4) parallel data structures that must be updated together (a TypeScript interface and a Python Pydantic model with the same fields), (5) Ctrl+F your way to copies — if you search for a business term and it appears in more than one implementation, that's a candidate.
Key Takeaways
🎯 Core Concept: DRY states that every piece of knowledge in a system — logic, rules, data values, intent — must have a single, unambiguous, authoritative representation. Every other part of the system refers to that representation rather than re-implementing it. Changes propagate automatically from the single source to every consumer, making it impossible to update one copy while leaving another stale.
🔑 Key Benefits:
Single point of change: Update one place, propagate everywhere — no search-and-replace risk
Divergence prevention: Two copies of the same rule will inevitably drift; one copy cannot
Smaller surface area: Less code to read, test, and maintain
Named intent: Constants and extracted functions name the why, not just the what
Compound confidence: A test that passes for the single authoritative implementation passes for all consumers
🌐 Language-Specific Highlights:
Python: Use module-level constants for magic numbers; @dataclass and @property to avoid duplicated computed values; ABC + @abstractmethod to enforce shared interfaces; type aliases to avoid repeated complex type annotations
TypeScript: Centralize types in a types/ folder and import them everywhere; as const enums for string constants; avoid parallel interface definitions on frontend and backend — use codegen from a shared schema
Java: Define constants in a dedicated BusinessRules or AppConstants class; use record for immutable value objects shared across the codebase; extract repeated Optional/null-check patterns into utility methods
JavaScript: Define API base URLs in a single apiClient module; use module-level const for configuration values; export shared utilities from a utils/ index file
C#: Use internal sealed classes for implementation, public interface for the shared contract; record types for immutable shared value objects; static extension methods for shared transformations
PHP: Use readonly class properties for value objects; centralize route constants and error messages in dedicated classes; use constructor property promotion to reduce boilerplate
Go: Define sentinel errors as package-level var — they become the single source for error identity checks; use const blocks for related constants; export only what's needed to keep consumers from depending on internal detail
Rust: Implement Display and From traits once instead of writing conversion logic in every callsite; use const for magic values; OnceLock for lazily-compiled regexes — one definition, zero-cost reuse
Dart: Use const constructors for design tokens and configuration objects; extension methods to add shared behavior to existing types without subclassing; typedef for shared function signatures
Swift: Define design tokens in a enum AppSpacing or struct Theme; use extension to add shared computed properties to built-in types; protocol + extension for default implementations shared across conforming types
Kotlin: Use object for singleton constants and utility namespaces; companion object for factory methods and constants tied to a type; extension functions for shared transformations; data class for value objects used in multiple layers
📋 When DRY Clearly Applies:
The same validation rule, calculation, or transformation appears in two or more places
A magic number or string literal appears more than twice without a name
Two data structures must always be updated together (they represent the same schema)
An error message or user-facing string is hardcoded in multiple handlers
The same null-check, error-handling, or logging pattern is copy-pasted across multiple functions
⚠️ When to Hold Off:
You have only two occurrences and the "right" abstraction isn't obvious — wait for the third (Rule of Three)
Two pieces of code look similar but change for different reasons — merging them creates fragile coupling
Test code where duplication improves readability and independence of each test case
Performance-critical paths where the abstraction adds measurable overhead
🛠️ Best Practices Across Languages:
Name every magic value: A constant with a good name communicates intent and creates a single update point
Extract the pattern, not the code: Before extracting a function, ask if the two callers share the same business concept — if not, the similarity is accidental
Use code generation for cross-layer duplication: API types, database schema types, and configuration structures should be generated from a single source, not hand-maintained in parallel
Centralize configuration: All environment-specific values — URLs, timeouts, limits — live in one configuration module that is imported, never inlined
Let the Rule of Three guide extraction timing: Two occurrences is a note; three is a commitment
Distinguish essential from accidental similarity: Two functions that do the same thing for the same reason should be unified; two that look the same but serve different domains should remain separate
Document the single source: When a constant, function, or module is the authoritative source for a piece of knowledge, say so in the docstring — future developers need to know where to find it
💡 Common Pitfalls:
Merging accidentally similar logic: Two loops that iterate over different domains in the same way are not the same knowledge — merging them produces a fragile "general" function that breaks when either domain evolves
Hardcoding in tests: Test data that repeats magic numbers from production code creates a hidden coupling — if the production constant changes, tests may still pass while testing the wrong thing
Over-centralizing configuration: A single global config object that grows to hold every value in the system becomes a dependency magnet — group configuration by domain
DRY at the wrong layer: Sharing database queries between two services that happen to need the same data can couple microservices — sometimes the duplication is intentional isolation
Ignoring generated duplication: Checked-in generated files (ORM models, API clients) that diverge from their source schema are a DRY violation hiding in plain sight — regenerate on every schema change
The DRY principle is the foundation of maintainable software. Every duplicated piece of knowledge is a future bug waiting for a change request to trigger it. Applying DRY is not about aesthetic tidiness — it is about building a system where updating a business rule means updating one line, not hunting down every place that rule was re-implemented.
Want to explore related principles? Check out our guides on:
SSOT (Single Source of Truth)Each guide includes examples in all 11 programming languages with language-specific best practices.
)\\n usernameRegex = regexp.MustCompile(`^[a-zA-Z0-9_]+
Don't Repeat Yourself (DRY) | Design Patterns In Action
)\\n)\\n\\n// ValidationResult represents the outcome of a single validation rule.\\ntype ValidationResult struct {\\n Valid bool\\n Error string\\n}\\n\\nvar validOK = ValidationResult{Valid: true}\\n\\nfunc ok() ValidationResult { return validOK }\\nfunc fail(msg string) ValidationResult { return ValidationResult{Valid: false, Error: msg} }\\n\\n// ── Single authoritative validators ────────────────────────────\\n\\nfunc validateEmail(email string) ValidationResult {\\n if !emailRegex.MatchString(email) { return fail(\\\"Invalid email address\\\") }\\n if len(email) \u003e 255 { return fail(\\\"Email address too long\\\") }\\n return ok()\\n}\\n\\nfunc validateUsername(username string) ValidationResult {\\n if l := len(username); l \u003c 3 || l \u003e 30 { return fail(\\\"Username must be 3–30 characters\\\") }\\n if !usernameRegex.MatchString(username) { return fail(\\\"Username: letters, numbers, underscores only\\\") }\\n return ok()\\n}\\n\\nfunc validatePassword(password string) ValidationResult {\\n if len(password) \u003c 8 { return fail(\\\"Password must be at least 8 characters\\\") }\\n hasUpper := false\\n for _, r := range password {\\n if unicode.IsUpper(r) { hasUpper = true; break }\\n }\\n if !hasUpper { return fail(\\\"Password must contain an uppercase letter\\\") }\\n return ok()\\n}\\n\\nfunc firstFailure(results ...ValidationResult) ValidationResult {\\n for _, r := range results {\\n if !r.Valid { return r }\\n }\\n return ok()\\n}\\n\\n// ── Handlers that reference — never re-implement ───────────────\\n\\nfunc registerUser(email, username, password string) string {\\n if r := firstFailure(validateEmail(email), validateUsername(username), validatePassword(password)); !r.Valid {\\n return r.Error\\n }\\n return \\\"OK\\\"\\n}\\n\\nfunc updateProfile(email, username string) string {\\n if r := firstFailure(validateEmail(email), validateUsername(username)); !r.Valid {\\n return r.Error\\n }\\n return \\\"OK\\\"\\n}\\n\\nfunc resetPassword(password, confirm string) string {\\n if r := validatePassword(password); !r.Valid { return r.Error }\\n if password != confirm { return \\\"Passwords do not match\\\" }\\n return \\\"OK\\\"\\n}\\n\\n// ── Client ─────────────────────────────────────────────────────\\nfunc main() {\\n fmt.Println(\\\"Register (valid):\\\", registerUser(\\\"jane@example.com\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\"))\\n fmt.Println(\\\"Register (bad email):\\\", registerUser(\\\"not-an-email\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\"))\\n fmt.Println(\\\"Update profile:\\\", updateProfile(\\\"jane@example.com\\\", \\\"jane_doe\\\"))\\n fmt.Println(\\\"Reset password:\\\", resetPassword(\\\"SecurePass1\\\", \\\"SecurePass1\\\"))\\n}\\n```\\n\\n### 🦀 Rust\\n\\n```rust\\nuse regex::Regex;\\nuse std::sync::OnceLock;\\n\\n// ════════════════════════════════════════════════════════════════\\n// ✅ DRY VERSION — Validators as functions with a shared result type\\n// ════════════════════════════════════════════════════════════════\\n\\n// Single type used across all validators — no duplication of error representation\\ntype ValidationResult = Result\u003c(), String\u003e;\\n\\nfn ok() -\u003e ValidationResult { Ok(()) }\\nfn fail(msg: \u0026str) -\u003e ValidationResult { Err(msg.to_string()) }\\n\\n// Lazy-compiled regexes — single definition, reused everywhere\\nstatic EMAIL_RE: OnceLock\u003cRegex\u003e = OnceLock::new();\\nstatic USERNAME_RE: OnceLock\u003cRegex\u003e = OnceLock::new();\\n\\nfn email_re() -\u003e \u0026'static Regex { EMAIL_RE.get_or_init(|| Regex::new(r\\\"^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$\\\").unwrap()) }\\nfn username_re() -\u003e \u0026'static Regex { USERNAME_RE.get_or_init(|| Regex::new(r\\\"^[a-zA-Z0-9_]+$\\\").unwrap()) }\\n\\n// ── Single authoritative validators ────────────────────────────\\n\\nfn validate_email(email: \u0026str) -\u003e ValidationResult {\\n if !email_re().is_match(email) { return fail(\\\"Invalid email address\\\"); }\\n if email.len() \u003e 255 { return fail(\\\"Email address too long\\\"); }\\n ok()\\n}\\n\\nfn validate_username(username: \u0026str) -\u003e ValidationResult {\\n let len = username.len();\\n if !(3..=30).contains(\u0026len) { return fail(\\\"Username must be 3–30 characters\\\"); }\\n if !username_re().is_match(username) { return fail(\\\"Username: letters, numbers, underscores only\\\"); }\\n ok()\\n}\\n\\nfn validate_password(password: \u0026str) -\u003e ValidationResult {\\n if password.len() \u003c 8 { return fail(\\\"Password must be at least 8 characters\\\"); }\\n if !password.chars().any(|c| c.is_uppercase()) { return fail(\\\"Password must contain an uppercase letter\\\"); }\\n ok()\\n}\\n\\nfn first_failure(results: Vec\u003cValidationResult\u003e) -\u003e ValidationResult {\\n results.into_iter().find(|r| r.is_err()).unwrap_or(ok())\\n}\\n\\n// ── Handlers that reference — never re-implement ───────────────\\n\\nfn register_user(email: \u0026str, username: \u0026str, password: \u0026str) -\u003e String {\\n match first_failure(vec![validate_email(email), validate_username(username), validate_password(password)]) {\\n Ok(()) =\u003e \\\"OK\\\".to_string(),\\n Err(msg) =\u003e msg,\\n }\\n}\\n\\nfn update_profile(email: \u0026str, username: \u0026str) -\u003e String {\\n match first_failure(vec![validate_email(email), validate_username(username)]) {\\n Ok(()) =\u003e \\\"OK\\\".to_string(),\\n Err(msg) =\u003e msg,\\n }\\n}\\n\\nfn reset_password(password: \u0026str, confirm: \u0026str) -\u003e String {\\n if let Err(msg) = validate_password(password) { return msg; }\\n if password != confirm { return \\\"Passwords do not match\\\".to_string(); }\\n \\\"OK\\\".to_string()\\n}\\n\\n// ── Client ─────────────────────────────────────────────────────\\nfn main() {\\n println!(\\\"Register (valid): {}\\\", register_user(\\\"jane@example.com\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\"));\\n println!(\\\"Register (bad email):{}\\\", register_user(\\\"not-an-email\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\"));\\n println!(\\\"Update profile: {}\\\", update_profile(\\\"jane@example.com\\\", \\\"jane_doe\\\"));\\n println!(\\\"Reset password: {}\\\", reset_password(\\\"SecurePass1\\\", \\\"SecurePass1\\\"));\\n}\\n```\\n\\n### 🎯 Dart\\n\\n```dart\\nimport 'dart:core';\\n\\n// ════════════════════════════════════════════════════════════════\\n// ✅ DRY VERSION — Single validators, shared by all handlers\\n// ════════════════════════════════════════════════════════════════\\n\\nsealed class ValidationResult {\\n const ValidationResult();\\n}\\nclass Valid extends ValidationResult { const Valid(); }\\nclass Invalid extends ValidationResult {\\n final String error;\\n const Invalid(this.error);\\n}\\n\\nconst ok = Valid();\\nInvalid fail(String error) =\u003e Invalid(error);\\n\\n// ── Single authoritative validators ────────────────────────────\\n\\nfinal _emailRegex = RegExp(r'^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+
);\\nfinal _usernameRegex = RegExp(r'^[a-zA-Z0-9_]+
);\\n\\nValidationResult validateEmail(String email) {\\n if (!_emailRegex.hasMatch(email)) return fail('Invalid email address');\\n if (email.length \u003e 255) return fail('Email address too long');\\n return ok;\\n}\\n\\nValidationResult validateUsername(String username) {\\n if (username.length \u003c 3 || username.length \u003e 30)\\n return fail('Username must be 3–30 characters');\\n if (!_usernameRegex.hasMatch(username))\\n return fail('Username: letters, numbers, underscores only');\\n return ok;\\n}\\n\\nValidationResult validatePassword(String password) {\\n if (password.length \u003c 8) return fail('Password must be at least 8 characters');\\n if (!password.contains(RegExp(r'[A-Z]')))\\n return fail('Password must contain an uppercase letter');\\n return ok;\\n}\\n\\nValidationResult firstFailure(List\u003cValidationResult\u003e results) =\u003e\\n results.firstWhere((r) =\u003e r is Invalid, orElse: () =\u003e ok);\\n\\n// ── Handlers that reference — never re-implement ───────────────\\n\\nString registerUser(String email, String username, String password) {\\n final result = firstFailure([validateEmail(email), validateUsername(username), validatePassword(password)]);\\n return result is Invalid ? result.error : 'OK';\\n}\\n\\nString updateProfile(String email, String username) {\\n final result = firstFailure([validateEmail(email), validateUsername(username)]);\\n return result is Invalid ? result.error : 'OK';\\n}\\n\\nString resetPassword(String password, String confirm) {\\n final result = validatePassword(password);\\n if (result is Invalid) return result.error;\\n if (password != confirm) return 'Passwords do not match';\\n return 'OK';\\n}\\n\\n// ── Client ─────────────────────────────────────────────────────\\nvoid main() {\\n print('Register (valid): ${registerUser(\\\"jane@example.com\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\")}');\\n print('Register (bad email):${registerUser(\\\"not-an-email\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\")}');\\n print('Update profile: ${updateProfile(\\\"jane@example.com\\\", \\\"jane_doe\\\")}');\\n print('Reset password: ${resetPassword(\\\"SecurePass1\\\", \\\"SecurePass1\\\")}');\\n}\\n```\\n\\n### 🍎 Swift\\n\\n```swift\\nimport Foundation\\n\\n// ════════════════════════════════════════════════════════════════\\n// ✅ DRY VERSION — Validators as enums/structs, one per rule set\\n// ════════════════════════════════════════════════════════════════\\n\\nenum ValidationResult {\\n case valid\\n case invalid(String)\\n\\n var isValid: Bool { if case .valid = self { return true }; return false }\\n var error: String? { if case .invalid(let msg) = self { return msg }; return nil }\\n}\\n\\n// ── Single authoritative validators ────────────────────────────\\n\\nstruct EmailValidator {\\n private static let pattern = try! NSRegularExpression(pattern: #\\\"^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$\\\"#)\\n\\n static func validate(_ email: String) -\u003e ValidationResult {\\n let range = NSRange(email.startIndex..., in: email)\\n if pattern.firstMatch(in: email, range: range) == nil { return .invalid(\\\"Invalid email address\\\") }\\n if email.count \u003e 255 { return .invalid(\\\"Email address too long\\\") }\\n return .valid\\n }\\n}\\n\\nstruct UsernameValidator {\\n private static let pattern = try! NSRegularExpression(pattern: \\\"^[a-zA-Z0-9_]+$\\\")\\n\\n static func validate(_ username: String) -\u003e ValidationResult {\\n if !(3...30).contains(username.count) { return .invalid(\\\"Username must be 3–30 characters\\\") }\\n let range = NSRange(username.startIndex..., in: username)\\n if pattern.firstMatch(in: username, range: range) == nil {\\n return .invalid(\\\"Username: letters, numbers, underscores only\\\")\\n }\\n return .valid\\n }\\n}\\n\\nstruct PasswordValidator {\\n static func validate(_ password: String) -\u003e ValidationResult {\\n if password.count \u003c 8 { return .invalid(\\\"Password must be at least 8 characters\\\") }\\n if !password.contains(where: \\\\.isUppercase) { return .invalid(\\\"Password must contain an uppercase letter\\\") }\\n return .valid\\n }\\n}\\n\\nfunc firstFailure(_ results: ValidationResult...) -\u003e ValidationResult {\\n results.first(where: { !$0.isValid }) ?? .valid\\n}\\n\\n// ── Handlers that reference — never re-implement ───────────────\\n\\nfunc registerUser(email: String, username: String, password: String) -\u003e String {\\n let result = firstFailure(EmailValidator.validate(email), UsernameValidator.validate(username), PasswordValidator.validate(password))\\n return result.error ?? \\\"OK\\\"\\n}\\n\\nfunc updateProfile(email: String, username: String) -\u003e String {\\n let result = firstFailure(EmailValidator.validate(email), UsernameValidator.validate(username))\\n return result.error ?? \\\"OK\\\"\\n}\\n\\nfunc resetPassword(password: String, confirm: String) -\u003e String {\\n if let error = PasswordValidator.validate(password).error { return error }\\n if password != confirm { return \\\"Passwords do not match\\\" }\\n return \\\"OK\\\"\\n}\\n\\n// ── Client ─────────────────────────────────────────────────────\\nprint(\\\"Register (valid): \\\\(registerUser(email: \\\"jane@example.com\\\", username: \\\"jane_doe\\\", password: \\\"SecurePass1\\\"))\\\")\\nprint(\\\"Register (bad email):\\\\(registerUser(email: \\\"not-an-email\\\", username: \\\"jane_doe\\\", password: \\\"SecurePass1\\\"))\\\")\\nprint(\\\"Update profile: \\\\(updateProfile(email: \\\"jane@example.com\\\", username: \\\"jane_doe\\\"))\\\")\\nprint(\\\"Reset password: \\\\(resetPassword(password: \\\"SecurePass1\\\", confirm: \\\"SecurePass1\\\"))\\\")\\n```\\n\\n### 🎪 Kotlin\\n\\n```kotlin\\n// ════════════════════════════════════════════════════════════════\\n// ✅ DRY VERSION — Sealed class result type, object validators\\n// ════════════════════════════════════════════════════════════════\\n\\nsealed class ValidationResult {\\n object Valid : ValidationResult()\\n data class Invalid(val error: String) : ValidationResult()\\n val isValid get() = this is Valid\\n val error get() = (this as? Invalid)?.error\\n}\\n\\nfun ok() = ValidationResult.Valid\\nfun fail(msg: String) = ValidationResult.Invalid(msg)\\n\\n// ── Single authoritative validators ────────────────────────────\\n\\nobject EmailValidator {\\n private val regex = Regex(\\\"\\\"\\\"^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$\\\"\\\"\\\")\\n\\n fun validate(email: String): ValidationResult {\\n if (!regex.matches(email)) return fail(\\\"Invalid email address\\\")\\n if (email.length \u003e 255) return fail(\\\"Email address too long\\\")\\n return ok()\\n }\\n}\\n\\nobject UsernameValidator {\\n private val regex = Regex(\\\"^[a-zA-Z0-9_]+$\\\")\\n\\n fun validate(username: String): ValidationResult {\\n if (username.length !in 3..30) return fail(\\\"Username must be 3–30 characters\\\")\\n if (!regex.matches(username)) return fail(\\\"Username: letters, numbers, underscores only\\\")\\n return ok()\\n }\\n}\\n\\nobject PasswordValidator {\\n fun validate(password: String): ValidationResult {\\n if (password.length \u003c 8) return fail(\\\"Password must be at least 8 characters\\\")\\n if (password.none { it.isUpperCase() }) return fail(\\\"Password must contain an uppercase letter\\\")\\n return ok()\\n }\\n}\\n\\nfun firstFailure(vararg results: ValidationResult) = results.firstOrNull { !it.isValid } ?: ok()\\n\\n// ── Handlers that reference — never re-implement ───────────────\\n\\nfun registerUser(email: String, username: String, password: String): String {\\n val result = firstFailure(EmailValidator.validate(email), UsernameValidator.validate(username), PasswordValidator.validate(password))\\n return result.error ?: \\\"OK\\\"\\n}\\n\\nfun updateProfile(email: String, username: String): String {\\n val result = firstFailure(EmailValidator.validate(email), UsernameValidator.validate(username))\\n return result.error ?: \\\"OK\\\"\\n}\\n\\nfun resetPassword(password: String, confirm: String): String {\\n PasswordValidator.validate(password).error?.let { return it }\\n if (password != confirm) return \\\"Passwords do not match\\\"\\n return \\\"OK\\\"\\n}\\n\\n// ── Client ─────────────────────────────────────────────────────\\nfun main() {\\n println(\\\"Register (valid): ${registerUser(\\\"jane@example.com\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\")}\\\")\\n println(\\\"Register (bad email):${registerUser(\\\"not-an-email\\\", \\\"jane_doe\\\", \\\"SecurePass1\\\")}\\\")\\n println(\\\"Update profile: ${updateProfile(\\\"jane@example.com\\\", \\\"jane_doe\\\")}\\\")\\n println(\\\"Reset password: ${resetPassword(\\\"SecurePass1\\\", \\\"SecurePass1\\\")}\\\")\\n}\\n```\\n\\n---\\n\\n## Production-Ready Example\\n\\nNow let's build a **pricing engine** — a richer, production-grade application of the DRY principle. An e-commerce platform applies tax rates, discount rules, and price formatting consistently across order summaries, invoices, cart previews, and admin reports. The WET approach scatters these calculations and constants everywhere. The DRY approach puts each piece of pricing knowledge in one authoritative location — and all consumers derive their behavior from it.\\n\\n### 🐍 Python\\n\\n```python\\nfrom __future__ import annotations\\nfrom dataclasses import dataclass, field\\nfrom decimal import Decimal, ROUND_HALF_UP\\nfrom typing import Optional\\nfrom enum import Enum\\n\\n\\n# ════════════════════════════════════════════════════════════════\\n# SINGLE SOURCE — Pricing knowledge lives here and only here\\n# ════════════════════════════════════════════════════════════════\\n\\nclass TaxRegion(Enum):\\n US_STANDARD = \\\"us_standard\\\"\\n EU_VAT = \\\"eu_vat\\\"\\n UK_VAT = \\\"uk_vat\\\"\\n TAX_EXEMPT = \\\"tax_exempt\\\"\\n\\n\\n# ── Single authoritative tax rates ─────────────────────────────\\nTAX_RATES: dict[TaxRegion, Decimal] = {\\n TaxRegion.US_STANDARD: Decimal(\\\"0.0875\\\"), # 8.75%\\n TaxRegion.EU_VAT: Decimal(\\\"0.20\\\"), # 20%\\n TaxRegion.UK_VAT: Decimal(\\\"0.20\\\"), # 20%\\n TaxRegion.TAX_EXEMPT: Decimal(\\\"0.00\\\"),\\n}\\n\\n# ── Single authoritative discount tiers ────────────────────────\\nDISCOUNT_TIERS: list[tuple[Decimal, Decimal]] = [\\n (Decimal(\\\"1000\\\"), Decimal(\\\"0.15\\\")), # 15% off orders over $1000\\n (Decimal(\\\"500\\\"), Decimal(\\\"0.10\\\")), # 10% off orders over $500\\n (Decimal(\\\"200\\\"), Decimal(\\\"0.05\\\")), # 5% off orders over $200\\n]\\n\\n# ── Single authoritative rounding rule ─────────────────────────\\ndef round_currency(amount: Decimal) -\u003e Decimal:\\n \\\"\\\"\\\"All monetary rounding in the system uses this one function.\\\"\\\"\\\"\\n return amount.quantize(Decimal(\\\"0.01\\\"), rounding=ROUND_HALF_UP)\\n\\n# ── Single authoritative discount calculator ───────────────────\\ndef calculate_discount(subtotal: Decimal) -\u003e Decimal:\\n \\\"\\\"\\\"Single source of truth for volume discount logic.\\\"\\\"\\\"\\n for threshold, rate in DISCOUNT_TIERS:\\n if subtotal \u003e= threshold:\\n return round_currency(subtotal * rate)\\n return Decimal(\\\"0.00\\\")\\n\\n# ── Single authoritative tax calculator ────────────────────────\\ndef calculate_tax(amount_after_discount: Decimal, region: TaxRegion) -\u003e Decimal:\\n \\\"\\\"\\\"Single source of truth for tax calculation.\\\"\\\"\\\"\\n rate = TAX_RATES[region]\\n return round_currency(amount_after_discount * rate)\\n\\n# ── Single authoritative price formatter ───────────────────────\\ndef format_price(amount: Decimal) -\u003e str:\\n \\\"\\\"\\\"All price display in the system uses this one formatter.\\\"\\\"\\\"\\n return f\\\"${amount:,.2f}\\\"\\n\\n\\n# ════════════════════════════════════════════════════════════════\\n# CONSUMERS — All derive from the single source above\\n# ════════════════════════════════════════════════════════════════\\n\\n@dataclass\\nclass LineItem:\\n name: str\\n unit_price: Decimal\\n quantity: int\\n\\n @property\\n def subtotal(self) -\u003e Decimal:\\n return round_currency(self.unit_price * self.quantity)\\n\\n\\n@dataclass\\nclass PricingBreakdown:\\n subtotal: Decimal\\n discount: Decimal\\n tax: Decimal\\n total: Decimal\\n region: TaxRegion\\n\\n def display(self, title: str = \\\"Pricing Summary\\\") -\u003e None:\\n print(f\\\"\\\\n ── {title} ──\\\")\\n print(f\\\" Subtotal: {format_price(self.subtotal)}\\\") # Uses single formatter\\n if self.discount \u003e 0:\\n print(f\\\" Discount: -{format_price(self.discount)}\\\")\\n print(f\\\" Tax ({self.region.value}): {format_price(self.tax)}\\\")\\n print(f\\\" ─────────────────────\\\")\\n print(f\\\" Total: {format_price(self.total)}\\\")\\n\\n\\ndef price_order(items: list[LineItem], region: TaxRegion) -\u003e PricingBreakdown:\\n \\\"\\\"\\\"Uses every single-source function — no inline calculation.\\\"\\\"\\\"\\n subtotal = round_currency(sum(item.subtotal for item in items))\\n discount = calculate_discount(subtotal) # Single source\\n after_discount = subtotal - discount\\n tax = calculate_tax(after_discount, region) # Single source\\n total = after_discount + tax\\n return PricingBreakdown(subtotal, discount, tax, total, region)\\n\\n\\n# ── Multiple consumers — all using the same single sources ──────\\n\\ndef generate_cart_preview(items: list[LineItem], region: TaxRegion) -\u003e str:\\n breakdown = price_order(items, region)\\n # Uses format_price — the single formatter — no inline formatting\\n return f\\\"Cart: {format_price(breakdown.subtotal)} → Total: {format_price(breakdown.total)}\\\"\\n\\n\\ndef generate_invoice_line(items: list[LineItem], region: TaxRegion) -\u003e str:\\n breakdown = price_order(items, region)\\n tax_rate = TAX_RATES[region] * 100 # Single source for rate display\\n return (\\n f\\\"Invoice | Sub: {format_price(breakdown.subtotal)} | \\\"\\n f\\\"Disc: {format_price(breakdown.discount)} | \\\"\\n f\\\"Tax({tax_rate:.1f}%): {format_price(breakdown.tax)} | \\\"\\n f\\\"Total: {format_price(breakdown.total)}\\\"\\n )\\n\\n\\n# ── Client ─────────────────────────────────────────────────────\\nif __name__ == \\\"__main__\\\":\\n items = [\\n LineItem(\\\"Laptop\\\", Decimal(\\\"899.99\\\"), 1),\\n LineItem(\\\"Keyboard\\\", Decimal(\\\"149.99\\\"), 2),\\n LineItem(\\\"Monitor\\\", Decimal(\\\"399.00\\\"), 1),\\n ]\\n\\n print(\\\"=== DRY Pricing Engine ===\\\")\\n for region in [TaxRegion.US_STANDARD, TaxRegion.EU_VAT, TaxRegion.TAX_EXEMPT]:\\n breakdown = price_order(items, region)\\n breakdown.display(f\\\"Order — {region.value}\\\")\\n\\n print(\\\"\\\\n\\\" + generate_cart_preview(items, TaxRegion.US_STANDARD))\\n print(generate_invoice_line(items, TaxRegion.EU_VAT))\\n # If the US tax rate changes, update TAX_RATES once.\\n # Cart preview, invoice, order summary, and admin report all update automatically.\\n```\\n\\n---\\n\\n## Real-World Use Cases\\n\\nThe DRY principle applies far beyond validation and pricing. Here are the most impactful areas where DRY prevents the most bugs.\\n\\n**Configuration and constants**: Magic numbers and strings embedded across a codebase are a DRY violation. `MAX_RETRY_ATTEMPTS = 3` defined once, referenced everywhere, is DRY. The same `3` copy-pasted into five retry loops, with one loop that was later updated to `5` while the others weren't, is a real production bug with a real outage.\\n\\n**API contracts and types**: In full-stack applications, the API request/response shape is duplicated when the backend defines it in Python and the frontend defines it again in TypeScript. Code generation from a single schema (OpenAPI, GraphQL, Protobuf) eliminates this. When the schema changes, both backend and frontend types regenerate from the same source — they cannot diverge.\\n\\n**Database queries**: An application that constructs the same SQL fragment — `WHERE deleted_at IS NULL AND active = 1` — inline in a dozen query functions has duplicated business knowledge about what \\\"active\\\" means. A single `active_users` query builder or named scope is the DRY solution. When the definition of \\\"active\\\" changes (adding a `suspended` flag, for instance), one change propagates everywhere.\\n\\n**Business rules in multiple tiers**: Discount eligibility rules that exist in the backend (for processing) and also in the frontend (for display) and also in the database (as a CHECK constraint) are triplicated. A single canonical rule — even if it must be compiled to different environments — reduces the three places that must be updated on every rule change to one authoritative source.\\n\\n**Error messages**: User-facing error strings hardcoded in multiple places will inevitably say different things for the same error in different parts of the application. A centralized message registry ensures every consumer says exactly the same thing for the same condition.\\n\\n---\\n\\n## Language-Specific Mistakes and Anti-Patterns\\n\\n### 🐍 Python Mistakes\\n\\n#### ❌ Mistake #1: Magic Numbers Scattered Across Functions\\n\\n```python\\n# BAD: 8 appears in three separate places — if the minimum changes, which do you update?\\ndef register_user(password: str) -\u003e bool:\\n return len(password) \u003e= 8 # ← knowledge\\n\\ndef validate_api_key(key: str) -\u003e bool:\\n return len(key) \u003e= 8 # ← different knowledge that looks identical\\n\\ndef reset_password(new_password: str) -\u003e bool:\\n if len(new_password) \u003c 8: # ← same password knowledge, duplicated\\n raise ValueError(\\\"Too short\\\")\\n return True\\n\\n# GOOD: Named constant — one authoritative definition\\nMIN_PASSWORD_LENGTH = 8 # Change here → changes everywhere\\n\\ndef validate_password_length(password: str) -\u003e bool:\\n return len(password) \u003e= MIN_PASSWORD_LENGTH\\n\\n# register_user and reset_password both call validate_password_length\\n```\\n\\n#### ❌ Mistake #2: Duplicated Formatting Logic\\n\\n```python\\n# BAD: Currency formatting repeated everywhere\\ndef show_cart(total: float) -\u003e str:\\n return f\\\"Total: ${total:.2f}\\\" # ← formatting knowledge\\n\\ndef print_receipt(amount: float) -\u003e str:\\n return f\\\"Charged: ${amount:.2f}\\\" # ← same formatting, duplicated\\n\\ndef send_invoice(price: float) -\u003e str:\\n return f\\\"Amount: ${price:.2f}\\\" # ← third copy\\n\\n# GOOD: Single formatter\\ndef format_currency(amount: float) -\u003e str:\\n \\\"\\\"\\\"Single source for all monetary display formatting.\\\"\\\"\\\"\\n return f\\\"${amount:,.2f}\\\"\\n\\ndef show_cart(total: float) -\u003e str: return f\\\"Total: {format_currency(total)}\\\"\\ndef print_receipt(amount: float) -\u003e str: return f\\\"Charged: {format_currency(amount)}\\\"\\ndef send_invoice(price: float) -\u003e str: return f\\\"Amount: {format_currency(price)}\\\"\\n```\\n\\n---\\n\\n### 📘 TypeScript Mistakes\\n\\n#### ❌ Mistake #1: Duplicated Type Definitions\\n\\n```typescript\\n// BAD: User shape defined in two places — must be kept in sync manually\\n// In api.ts:\\ninterface UserResponse { id: number; email: string; role: \\\"admin\\\" | \\\"user\\\"; }\\n\\n// In components/UserCard.tsx:\\ninterface User { id: number; email: string; role: \\\"admin\\\" | \\\"user\\\"; } // ← exact copy\\n\\n// GOOD: Define once, import everywhere\\n// In types/user.ts:\\nexport interface User { id: number; email: string; role: \\\"admin\\\" | \\\"user\\\"; }\\n\\n// In api.ts:\\nimport type { User } from \\\"../types/user\\\";\\n// In components/UserCard.tsx:\\nimport type { User } from \\\"../types/user\\\";\\n```\\n\\n#### ❌ Mistake #2: Inline String Literals for Statuses\\n\\n```typescript\\n// BAD: \\\"pending\\\", \\\"approved\\\", \\\"rejected\\\" repeated in guards, renders, API calls\\nfunction canApprove(status: string): boolean { return status === \\\"pending\\\"; }\\nfunction canReject(status: string): boolean { return status === \\\"pending\\\"; }\\nfunction statusLabel(status: string): string {\\n if (status === \\\"approved\\\") return \\\"Approved\\\";\\n if (status === \\\"rejected\\\") return \\\"Rejected\\\";\\n return \\\"Pending\\\";\\n}\\n\\n// GOOD: Enum / const object is the single source\\nconst OrderStatus = { PENDING: \\\"pending\\\", APPROVED: \\\"approved\\\", REJECTED: \\\"rejected\\\" } as const;\\ntype OrderStatusType = typeof OrderStatus[keyof typeof OrderStatus];\\n\\nfunction canApprove(status: OrderStatusType): boolean { return status === OrderStatus.PENDING; }\\nfunction canReject(status: OrderStatusType): boolean { return status === OrderStatus.PENDING; }\\n```\\n\\n---\\n\\n### ☕ Java Mistakes\\n\\n#### ❌ Mistake #1: Constants Defined as Literals in Multiple Classes\\n\\n```java\\n// BAD: MAX_ITEMS defined independently in three service classes\\nclass CartService { private static final int MAX_ITEMS = 50; }\\nclass WishlistService{ private static final int MAX_ITEMS = 50; }\\nclass OrderService { private static final int MAX_ITEMS = 50; } // Was updated to 100 once, inconsistently\\n\\n// GOOD: Single authoritative constants class\\nclass BusinessRules {\\n public static final int MAX_CART_ITEMS = 50;\\n // CartService, WishlistService, OrderService all reference BusinessRules.MAX_CART_ITEMS\\n}\\n```\\n\\n#### ❌ Mistake #2: Repeated null-check boilerplate\\n\\n```java\\n// BAD: Null-check + exception pattern copy-pasted into 10 service methods\\npublic User getUser(Long id) {\\n User user = userRepo.findById(id).orElse(null);\\n if (user == null) throw new ResourceNotFoundException(\\\"User not found: \\\" + id);\\n return user;\\n}\\npublic Product getProduct(Long id) {\\n Product p = productRepo.findById(id).orElse(null);\\n if (p == null) throw new ResourceNotFoundException(\\\"Product not found: \\\" + id); // ← copy\\n return p;\\n}\\n\\n// GOOD: Extract the pattern once\\nprivate static \u003cT\u003e T requireFound(Optional\u003cT\u003e opt, String message) {\\n return opt.orElseThrow(() -\u003e new ResourceNotFoundException(message));\\n}\\n\\npublic User getUser(Long id) { return requireFound(userRepo.findById(id), \\\"User not found: \\\" + id); }\\npublic Product getProduct(Long id) { return requireFound(productRepo.findById(id), \\\"Product not found: \\\" + id); }\\n```\\n\\n---\\n\\n### 🌐 JavaScript Mistakes\\n\\n#### ❌ Mistake: Repeated URL construction\\n\\n```javascript\\n// BAD: Base URL and path fragments copy-pasted across fetch calls\\nasync function getUser(id) { return fetch(`https://api.example.com/v1/users/${id}`); }\\nasync function updateUser(id) { return fetch(`https://api.example.com/v1/users/${id}`, { method: \\\"PUT\\\" }); }\\nasync function getOrders() { return fetch(`https://api.example.com/v1/orders`); }\\n\\n// GOOD: Base URL defined once; paths are single-source\\nconst API_BASE = \\\"https://api.example.com/v1\\\";\\n\\nconst api = {\\n get: (path) =\u003e fetch(`${API_BASE}${path}`),\\n put: (path, body) =\u003e fetch(`${API_BASE}${path}`, { method: \\\"PUT\\\", body: JSON.stringify(body) }),\\n post: (path, body) =\u003e fetch(`${API_BASE}${path}`, { method: \\\"POST\\\", body: JSON.stringify(body) }),\\n};\\n\\nconst getUser = (id) =\u003e api.get(`/users/${id}`);\\nconst updateUser = (id) =\u003e api.put(`/users/${id}`);\\nconst getOrders = () =\u003e api.get(\\\"/orders\\\");\\n```\\n\\n---\\n\\n### 🔷 C# Mistakes\\n\\n#### ❌ Mistake: Duplicated mapping logic\\n\\n```csharp\\n// BAD: UserDto → User mapping written out in Controller AND in Service\\n// In UserController:\\nvar user = new User { Id = dto.Id, Email = dto.Email, Name = dto.Name };\\n\\n// In UserService (same mapping, different place):\\nvar user = new User { Id = request.Id, Email = request.Email, Name = request.Name };\\n\\n// GOOD: Single mapper class (or use AutoMapper)\\npublic static class UserMapper\\n{\\n public static User ToDomain(UserDto dto) =\u003e new() { Id = dto.Id, Email = dto.Email, Name = dto.Name };\\n}\\n// Controller and Service both call UserMapper.ToDomain()\\n```\\n\\n---\\n\\n### 🐘 PHP Mistakes\\n\\n#### ❌ Mistake: Repeated date formatting\\n\\n```php\\n// BAD: Same date format string in 5 templates\\necho date('Y-m-d H:i:s', $order-\u003ecreatedAt); // In OrderView\\necho date('Y-m-d H:i:s', $invoice-\u003eissuedAt); // In InvoiceView — copy\\necho date('Y-m-d H:i:s', $user-\u003eregisteredAt); // In UserView — copy\\n\\n// GOOD: Single helper — format string defined once\\nfunction formatTimestamp(int $timestamp): string\\n{\\n return date('Y-m-d H:i:s', $timestamp);\\n}\\n// All views call formatTimestamp() — change the format in one place\\n```\\n\\n---\\n\\n### 🐹 Go Mistakes\\n\\n#### ❌ Mistake: Error message strings duplicated\\n\\n```go\\n// BAD: \\\"user not found\\\" scattered as string literals\\nfunc GetUser(id int) (*User, error) {\\n if user == nil { return nil, errors.New(\\\"user not found\\\") }\\n}\\nfunc DeleteUser(id int) error {\\n if user == nil { return errors.New(\\\"user not found\\\") } // ← copy — might diverge\\n}\\n\\n// GOOD: Sentinel errors defined once\\nvar ErrUserNotFound = errors.New(\\\"user not found\\\")\\n\\nfunc GetUser(id int) (*User, error) { if user == nil { return nil, ErrUserNotFound } /* ... */ }\\nfunc DeleteUser(id int) error { if user == nil { return ErrUserNotFound } /* ... */ }\\n```\\n\\n---\\n\\n### 🦀 Rust Mistakes\\n\\n#### ❌ Mistake: Repeated match arms for the same conversion\\n\\n```rust\\n// BAD: Same status → string conversion in two display functions\\nfn order_label(status: \u0026Status) -\u003e \u0026str {\\n match status {\\n Status::Pending =\u003e \\\"Pending\\\",\\n Status::Approved =\u003e \\\"Approved\\\",\\n Status::Rejected =\u003e \\\"Rejected\\\",\\n }\\n}\\nfn invoice_label(status: \u0026Status) -\u003e \u0026str { // ← exact copy\\n match status {\\n Status::Pending =\u003e \\\"Pending\\\",\\n Status::Approved =\u003e \\\"Approved\\\",\\n Status::Rejected =\u003e \\\"Rejected\\\",\\n }\\n}\\n\\n// GOOD: Implement Display — one authoritative conversion\\nuse std::fmt;\\nimpl fmt::Display for Status {\\n fn fmt(\u0026self, f: \u0026mut fmt::Formatter) -\u003e fmt::Result {\\n let label = match self {\\n Status::Pending =\u003e \\\"Pending\\\",\\n Status::Approved =\u003e \\\"Approved\\\",\\n Status::Rejected =\u003e \\\"Rejected\\\",\\n };\\n write!(f, \\\"{}\\\", label)\\n }\\n}\\n// Now both order_label and invoice_label use `format!(\\\"{}\\\", status)`\\n```\\n\\n---\\n\\n### 🎯 Dart Mistakes\\n\\n#### ❌ Mistake: Repeated widget padding values\\n\\n```dart\\n// BAD: Padding value repeated across widgets — one was updated, others weren't\\nPadding(padding: EdgeInsets.all(16), child: CartSummary());\\nPadding(padding: EdgeInsets.all(16), child: ProductCard());\\nPadding(padding: EdgeInsets.all(12), child: UserProfile()); // ← drifted — bug!\\n\\n// GOOD: Design token defined once\\nclass AppSpacing {\\n static const double cardPadding = 16.0;\\n}\\n\\nPadding(padding: EdgeInsets.all(AppSpacing.cardPadding), child: CartSummary());\\nPadding(padding: EdgeInsets.all(AppSpacing.cardPadding), child: ProductCard());\\nPadding(padding: EdgeInsets.all(AppSpacing.cardPadding), child: UserProfile());\\n```\\n\\n---\\n\\n### 🍎 Swift Mistakes\\n\\n#### ❌ Mistake: Duplicated URLRequest construction\\n\\n```swift\\n// BAD: Auth headers and base URL copy-pasted in every network call\\nfunc fetchUser(id: Int) async throws -\u003e User {\\n var request = URLRequest(url: URL(string: \\\"https://api.example.com/users/\\\\(id)\\\")!)\\n request.setValue(\\\"Bearer \\\\(token)\\\", forHTTPHeaderField: \\\"Authorization\\\")\\n request.setValue(\\\"application/json\\\", forHTTPHeaderField: \\\"Content-Type\\\")\\n // ...\\n}\\nfunc fetchOrders() async throws -\u003e [Order] {\\n var request = URLRequest(url: URL(string: \\\"https://api.example.com/orders\\\")!)\\n request.setValue(\\\"Bearer \\\\(token)\\\", forHTTPHeaderField: \\\"Authorization\\\") // ← copy\\n request.setValue(\\\"application/json\\\", forHTTPHeaderField: \\\"Content-Type\\\") // ← copy\\n // ...\\n}\\n\\n// GOOD: Single request builder\\nstruct APIClient {\\n private let baseURL = \\\"https://api.example.com\\\"\\n private let token: String\\n\\n func makeRequest(path: String) -\u003e URLRequest {\\n var request = URLRequest(url: URL(string: \\\"\\\\(baseURL)\\\\(path)\\\")!)\\n request.setValue(\\\"Bearer \\\\(token)\\\", forHTTPHeaderField: \\\"Authorization\\\")\\n request.setValue(\\\"application/json\\\", forHTTPHeaderField: \\\"Content-Type\\\")\\n return request\\n }\\n}\\n```\\n\\n---\\n\\n### 🎪 Kotlin Mistakes\\n\\n#### ❌ Mistake: Repeated coroutine error handling boilerplate\\n\\n```kotlin\\n// BAD: try/catch + logging copied into every suspend function\\nsuspend fun getUser(id: Long): User? {\\n return try {\\n userRepo.findById(id)\\n } catch (e: Exception) {\\n logger.error(\\\"Failed to fetch user $id\\\", e) // ← pattern\\n null\\n }\\n}\\nsuspend fun getOrder(id: Long): Order? {\\n return try {\\n orderRepo.findById(id)\\n } catch (e: Exception) {\\n logger.error(\\\"Failed to fetch order $id\\\", e) // ← same pattern, duplicated\\n null\\n }\\n}\\n\\n// GOOD: Extract the pattern once\\nsuspend fun \u003cT\u003e safeCall(context: String, block: suspend () -\u003e T): T? {\\n return try {\\n block()\\n } catch (e: Exception) {\\n logger.error(\\\"Failed: $context\\\", e)\\n null\\n }\\n}\\n\\nsuspend fun getUser(id: Long) = safeCall(\\\"user $id\\\") { userRepo.findById(id) }\\nsuspend fun getOrder(id: Long) = safeCall(\\\"order $id\\\") { orderRepo.findById(id) }\\n```\\n\\n---\\n\\n## Frequently Asked Questions\\n\\n### Q1: Is DRY just about avoiding copy-pasted code?\\n\\nNo — DRY is about *knowledge*, not just text. Two functions can have entirely different code but still violate DRY if they both independently implement the same business rule. Conversely, two functions that look similar but represent different rules should *not* be merged — that would create accidental coupling. The question to ask is: \\\"If this rule changes, should both places change in the same way?\\\" If yes, they're the same knowledge and should be unified. If no, they're different knowledge that happens to look similar.\\n\\n### Q2: Can DRY be over-applied?\\n\\nYes. The most common over-application is creating a \\\"universal\\\" abstraction from two pieces of code that happen to look similar but represent different things. When requirement A changes, you're forced to break the shared abstraction apart — leaving you worse off than if you'd never merged them. This is why the **Rule of Three** is a practical guide: one occurrence is fine, two is worth noting, three is reliably a pattern worth extracting.\\n\\n### Q3: What is the difference between DRY and abstraction?\\n\\nDRY is the *principle* — eliminate duplicated knowledge. Abstraction is the *mechanism* — functions, classes, modules, constants, generated types. DRY tells you *when* to abstract (when the same knowledge appears in more than one place). Abstraction gives you the *tools* to do it. DRY without abstraction is just an intention; abstraction without DRY gives you abstractions that don't correspond to real duplication.\\n\\n### Q4: How does DRY relate to the Single Responsibility Principle (SRP)?\\n\\nThey are complementary and mutually reinforcing. SRP says each module should have exactly one reason to change. DRY says each piece of knowledge should live in exactly one place. A DRY violation (the same rule in two places) usually means one of them will change while the other doesn't — which is also an SRP violation. Applying DRY by extracting knowledge into a single named location naturally produces SRP-compliant classes and functions.\\n\\n### Q5: Does DRY apply to tests?\\n\\nPartially. Test code is different from production code — readable, independent tests are valuable even if they share setup code. Excessive DRY in tests produces helper functions that obscure what each test is actually testing. The convention \\\"Arrange, Act, Assert\\\" in each test method, even at the cost of some duplication, is intentional. However, duplicated *test logic* (the assertion patterns, shared fixtures, setup that is clearly identical) should still be extracted into helpers. The threshold for extraction in tests is higher than in production code.\\n\\n### Q6: How do I identify DRY violations in a codebase I didn't write?\\n\\nSearch for: (1) the same regex or validation logic in multiple files, (2) the same magic number (`0.08`, `3`, `50`) appearing more than twice without a named constant, (3) the same error message string in multiple handlers, (4) parallel data structures that must be updated together (a TypeScript interface and a Python Pydantic model with the same fields), (5) `Ctrl+F` your way to copies — if you search for a business term and it appears in more than one implementation, that's a candidate.\\n\\n---\\n\\n## Key Takeaways\\n\\n🎯 **Core Concept**: DRY states that every piece of knowledge in a system — logic, rules, data values, intent — must have a single, unambiguous, authoritative representation. Every other part of the system *refers* to that representation rather than re-implementing it. Changes propagate automatically from the single source to every consumer, making it impossible to update one copy while leaving another stale.\\n\\n🔑 **Key Benefits**:\\n- **Single point of change**: Update one place, propagate everywhere — no search-and-replace risk\\n- **Divergence prevention**: Two copies of the same rule will inevitably drift; one copy cannot\\n- **Smaller surface area**: Less code to read, test, and maintain\\n- **Named intent**: Constants and extracted functions name the *why*, not just the *what*\\n- **Compound confidence**: A test that passes for the single authoritative implementation passes for all consumers\\n\\n🌐 **Language-Specific Highlights**:\\n- **Python**: Use module-level constants for magic numbers; `@dataclass` and `@property` to avoid duplicated computed values; `ABC` + `@abstractmethod` to enforce shared interfaces; type aliases to avoid repeated complex type annotations\\n- **TypeScript**: Centralize types in a `types/` folder and import them everywhere; `as const` enums for string constants; avoid parallel `interface` definitions on frontend and backend — use codegen from a shared schema\\n- **Java**: Define constants in a dedicated `BusinessRules` or `AppConstants` class; use `record` for immutable value objects shared across the codebase; extract repeated `Optional`/null-check patterns into utility methods\\n- **JavaScript**: Define API base URLs in a single `apiClient` module; use module-level `const` for configuration values; export shared utilities from a `utils/` index file\\n- **C#**: Use `internal sealed` classes for implementation, `public interface` for the shared contract; `record` types for immutable shared value objects; `static` extension methods for shared transformations\\n- **PHP**: Use `readonly` class properties for value objects; centralize route constants and error messages in dedicated classes; use constructor property promotion to reduce boilerplate\\n- **Go**: Define sentinel errors as package-level `var` — they become the single source for error identity checks; use `const` blocks for related constants; export only what's needed to keep consumers from depending on internal detail\\n- **Rust**: Implement `Display` and `From` traits once instead of writing conversion logic in every callsite; use `const` for magic values; `OnceLock` for lazily-compiled regexes — one definition, zero-cost reuse\\n- **Dart**: Use `const` constructors for design tokens and configuration objects; `extension` methods to add shared behavior to existing types without subclassing; typedef for shared function signatures\\n- **Swift**: Define design tokens in a `enum AppSpacing` or `struct Theme`; use `extension` to add shared computed properties to built-in types; `protocol` + `extension` for default implementations shared across conforming types\\n- **Kotlin**: Use `object` for singleton constants and utility namespaces; `companion object` for factory methods and constants tied to a type; `extension functions` for shared transformations; `data class` for value objects used in multiple layers\\n\\n📋 **When DRY Clearly Applies**:\\n- The same validation rule, calculation, or transformation appears in two or more places\\n- A magic number or string literal appears more than twice without a name\\n- Two data structures must always be updated together (they represent the same schema)\\n- An error message or user-facing string is hardcoded in multiple handlers\\n- The same null-check, error-handling, or logging pattern is copy-pasted across multiple functions\\n\\n⚠️ **When to Hold Off**:\\n- You have only two occurrences and the \\\"right\\\" abstraction isn't obvious — wait for the third (Rule of Three)\\n- Two pieces of code look similar but change for different reasons — merging them creates fragile coupling\\n- Test code where duplication improves readability and independence of each test case\\n- Performance-critical paths where the abstraction adds measurable overhead\\n\\n🛠️ **Best Practices Across Languages**:\\n1. **Name every magic value**: A constant with a good name communicates intent and creates a single update point\\n2. **Extract the pattern, not the code**: Before extracting a function, ask if the two callers share the *same business concept* — if not, the similarity is accidental\\n3. **Use code generation for cross-layer duplication**: API types, database schema types, and configuration structures should be generated from a single source, not hand-maintained in parallel\\n4. **Centralize configuration**: All environment-specific values — URLs, timeouts, limits — live in one configuration module that is imported, never inlined\\n5. **Let the Rule of Three guide extraction timing**: Two occurrences is a note; three is a commitment\\n6. **Distinguish essential from accidental similarity**: Two functions that do the same thing for the same reason should be unified; two that look the same but serve different domains should remain separate\\n7. **Document the single source**: When a constant, function, or module is the authoritative source for a piece of knowledge, say so in the docstring — future developers need to know where to find it\\n\\n💡 **Common Pitfalls**:\\n- **Merging accidentally similar logic**: Two loops that iterate over different domains in the same way are not the same knowledge — merging them produces a fragile \\\"general\\\" function that breaks when either domain evolves\\n- **Hardcoding in tests**: Test data that repeats magic numbers from production code creates a hidden coupling — if the production constant changes, tests may still pass while testing the wrong thing\\n- **Over-centralizing configuration**: A single global config object that grows to hold every value in the system becomes a dependency magnet — group configuration by domain\\n- **DRY at the wrong layer**: Sharing database queries between two services that happen to need the same data can couple microservices — sometimes the duplication is intentional isolation\\n- **Ignoring generated duplication**: Checked-in generated files (ORM models, API clients) that diverge from their source schema are a DRY violation hiding in plain sight — regenerate on every schema change\\n\\nThe DRY principle is the foundation of maintainable software. Every duplicated piece of knowledge is a future bug waiting for a change request to trigger it. Applying DRY is not about aesthetic tidiness — it is about building a system where updating a business rule means updating one line, not hunting down every place that rule was re-implemented.\\n\\n---\\n\\n*Want to explore related principles? Check out our guides on:*\\n- WET Principle (Write Everything Twice — the anti-pattern DRY prevents)\\n- OAOO (Once And Only Once)\\n- SSOT (Single Source of Truth)\\n\\n*Each guide includes examples in all 11 programming languages with language-specific best practices.*\\n\"}},\"actionData\":null,\"errors\":null}");