← Back to principles

Design Principle

DRY, KISS & YAGNI

Master DRY: eliminate duplication by extracting common logic. Learn when to apply this principle and how it improves maintainability and reduces bugs.

DRY, KISS & YAGNI

Why This Matters

Think of DRY, KISS, and YAGNI as three tools in your toolbox. DRY helps you avoid repeating yourself. KISS helps you keep things simple. YAGNI helps you avoid building things you don't need yet. They're not rules—they're tools for making better decisions.

This matters because code changes constantly. New features, bug fixes, refactoring—every change risks breaking something else. These principles help you write code that's easier to change. When you need to add a new feature, you shouldn't have to modify code in 10 different places. When you need to fix a bug, you shouldn't have to understand a complex abstraction that does too much.

In interviews, when someone asks "How would you refactor this code?", they're testing whether you understand these principles. Can you identify when code violates them? Can you refactor code to follow them? Most engineers can't. They write code that works, but it's hard to change.

What Engineers Usually Get Wrong

Engineers often think DRY means "never repeat code." But DRY is about eliminating duplication of knowledge, not code. Sometimes, the same code appears in two places for different reasons. Extracting it into a shared function might hide important context. The key is: if a bug fix needs to happen in multiple places, extract the shared logic.

Engineers also apply these principles too early. They create abstractions before they know what they need. It's better to start simple and refactor toward DRY/KISS/YAGNI as you learn what changes. Premature abstraction is as bad as no abstraction.

How This Breaks Systems in the Real World

A service had a PaymentProcessor class that handled credit cards, PayPal, and bank transfers. Initially, this was one class with if/else statements. It worked fine. But as the team added more payment methods, the class grew to 500 lines with nested if/else statements. Adding a new payment method required modifying this large class, risking breaking existing methods.

The fix? Apply Open/Closed Principle—create a PaymentMethod interface, with implementations for each payment type. Now, adding a new payment method means creating a new class, not modifying existing code. The lesson? DRY helps when code needs to change. Apply it when you see the need, not prematurely.

Another story: A team was building a feature that needed to process 100 items. A developer optimized it to handle 1 million items using complex algorithms and data structures. The optimization made the code hard to understand and debug. But the feature never processed more than 100 items. The optimization was unnecessary and made the code worse. The fix? Simplify to handle 100 items. Optimize only if you actually need to handle more. This is YAGNI in action.


DRY — Don’t Repeat Yourself

DRY is about deduplicating knowledge. Shared functionality should live in one place only if updates belong together.

1// Two endpoints validate discount codes differently.
2function applyCheckoutDiscount(code: string) {
3 if (code.length !== 10) throw new Error("Invalid");
4 if (!code.startsWith("PROMO-")) throw new Error("Unknown");
5}
6
7function applyRenewalDiscount(code: string) {
8 if (code.length !== 10) throw new Error("Invalid");

Marker: If a bug fix needs to land twice, reuse the knowledge once.


KISS — Keep It Simple, Ship It

KISS turns clever abstractions into clear flows. Optimise for readability over novelty; measure complexity in “how long to understand”.

1// Clever: chained factories and context-aware constructors.
2const user = User.create()
3 .withProfile(profileFactory("author"))
4 .withNotifications(enabledNotifications())
5 .build();
6
7// Simple: shape data clearly, let functions consume it.
8const user = {
9 profile: authorProfile(),
10 notifications: enabledNotifications(),
11};
12
13registerUser(user);

Marker: When onboarding teammates, note where explanations feel apologetic. That’s your KISS debt.


YAGNI — You Aren’t Gonna Need It

YAGNI keeps speculative architecture at bay. Delay features until a real user or system pressure demands them.

1// Premature generalisation: multi-provider plumbing without users.
2abstract class StorageProvider {
3 abstract put(key: string, value: string): Promise<void>;
4}
5
6class S3Storage extends StorageProvider {/* ... */}
7class GCSStorage extends StorageProvider {/* ... */}
8
9// Practical: solve today's need, keep the seam obvious.
10type Storage = {
11 put(key: string, value: string): Promise<void>;

Marker: If a counterfactual architecture has no customer, defer it. Document assumptions and revisit when evidence appears.


How They Interact

  • DRY vs. KISS: Don’t extract at the cost of clarity. If a helper function hides context, duplication might be kinder.
  • KISS vs. YAGNI: A simple solution is often the one you can delete later. Resist introducing indirection until the system insists.
  • DRY vs. YAGNI: Extract only after repetition proves intent. Otherwise you create abstractions nobody needs yet.

Reflection

  1. Track the last three bugs. Were they caused by duplication, complexity, or premature abstraction?
  2. Pair-program a refactor: remove one abstraction and measure whether reviews speed up.
  3. Establish a principle check in design docs: “What evidence says we need this now?”

Takeaway: DRY, KISS, and YAGNI keep you honest about trade-offs: share knowledge carefully, design for comprehension, and defer the future until it knocks.


Interview Questions

Beginner

Q: What does DRY stand for and why is it important? Give an example of code that violates DRY.

A: DRY stands for "Don't Repeat Yourself" - it means eliminating duplication of knowledge or logic. If the same business rule or validation appears in multiple places, extract it to a single source.

Example violation: Two functions validate discount codes with identical logic:

1function applyCheckoutDiscount(code: string) {
2 if (code.length !== 10) throw new Error("Invalid");
3 if (!code.startsWith("PROMO-")) throw new Error("Unknown");
4}
5
6function applyRenewalDiscount(code: string) {
7 if (code.length !== 10) throw new Error("Invalid");
8 if (code

Fix: Extract validateDiscount(code) and call it from both functions. If validation rules change, you only update one place.


Intermediate

Q: When should you NOT apply DRY? How do you balance DRY with KISS?

A: Don't apply DRY when:

  1. The duplication is coincidental (same code, different reasons to change)
  2. Extracting would hide important context or make code harder to understand
  3. The abstraction would be more complex than the duplication

Balance with KISS: If extracting shared logic creates a confusing abstraction that requires reading multiple files to understand, the duplication might be kinder. For example, two similar but contextually different validations shouldn't be forced into one generic validator if it obscures their distinct purposes. Measure complexity in "how long to understand" - if the DRY version takes longer to grasp, prefer KISS.


Senior

Q: You're designing a multi-tenant SaaS platform. How do you apply DRY, KISS, and YAGNI when building tenant isolation? What abstractions would you create now vs. defer?

A:

DRY: Extract tenant context resolution into a single middleware/service. Don't repeat tenant ID extraction in every endpoint.

KISS: Start with row-level security (add tenant_id to tables, filter queries). Don't build a complex multi-database architecture until you have evidence you need it.

YAGNI:

  • Defer: Cross-tenant analytics, tenant-specific database schemas, per-tenant infrastructure isolation
  • Build now: Basic tenant ID filtering, tenant context injection, simple data isolation

Abstractions to create now:

  • TenantContext interface for accessing current tenant
  • TenantAwareRepository base class that auto-filters by tenant_id
  • Middleware that extracts tenant from request and injects into context

Abstractions to defer:

  • Multi-database routing layer (wait until you have 100+ tenants and performance issues)
  • Tenant-specific feature flags system (wait until you have actual feature divergence requirements)
  • Cross-tenant data sharing mechanisms (wait until a customer actually requests it)

The key: Build the minimum viable tenant isolation that works, document assumptions, and revisit when evidence demands more complexity.


Failure Stories You'll Recognize

The Abstraction That Coupled Everything: A team created a DataAccess abstraction that worked for both database and file system. This seemed flexible. But the abstraction was leaky—database-specific concepts (transactions, connections) leaked into the interface. When the team needed database-specific features, they had to work around the abstraction. The abstraction made the code harder to understand and use. The fix? Use separate interfaces for database and file system. This is YAGNI—don't create abstractions until you need them.

The Premature Optimization: A team was building a feature that needed to process 100 items. A developer optimized it to handle 1 million items using complex algorithms and data structures. The optimization made the code hard to understand and debug. But the feature never processed more than 100 items. The optimization was unnecessary and made the code worse. The fix? Simplify to handle 100 items. Optimize only if you actually need to handle more. This is YAGNI in action.

The DRY Violation That Wasn't: Two functions had similar code, so a developer extracted it into a shared function. But the functions had different reasons to change—one was for user validation, the other for admin validation. When the requirements changed, the shared function became complex with conditional logic. The fix? Keep them separate. DRY is about eliminating duplication of knowledge, not code. If they change for different reasons, they should be separate.

What Interviewers Are Really Testing

They want to hear you talk about these principles as tools, not rigid rules. Junior engineers say "follow DRY, KISS, YAGNI." Senior engineers say "DRY helps eliminate duplication, but don't extract at the cost of clarity. KISS helps keep code simple, but simple doesn't mean incomplete. YAGNI helps avoid premature optimization, but don't use it as an excuse to write bad code."

When they ask "How would you refactor this code?", they're testing:

  • Can you identify violations of these principles?

  • Can you refactor code to follow them?

  • Do you understand when to apply them and when not to?

  • DRY eliminates duplication of knowledge - if a bug fix needs to happen twice, extract the shared logic once

  • KISS prioritizes clarity over cleverness - optimize for readability, measure complexity in "how long to understand"

  • YAGNI defers speculative features - delay capabilities until real user or system pressure demands them

  • DRY vs. KISS: Don't extract at the cost of clarity - if a helper hides context, duplication might be kinder

  • KISS vs. YAGNI: Simple solutions are often easier to delete later - resist indirection until the system insists

  • DRY vs. YAGNI: Extract only after repetition proves intent - otherwise you create abstractions nobody needs yet

  • Track bugs: Were they caused by duplication, complexity, or premature abstraction?

  • Establish principle checks in design docs: "What evidence says we need this now?"

How InterviewCrafted Will Teach This

We'll teach this through real code problems, not abstract definitions. Instead of memorizing "DRY means Don't Repeat Yourself," you'll learn through scenarios like "this code is duplicated—should I extract it?"

You'll see how these principles help you recognize and fix problems. When an interviewer asks "how would you refactor this code?", you'll think about duplication, complexity, and premature optimization—not just "apply DRY."

  • SOLID Principles - Core design principles that complement DRY, KISS, and YAGNI. SOLID focuses on object-oriented design, while these principles guide general code quality.
  • Composition over Inheritance - Related to KISS and YAGNI, composition often provides simpler, more flexible solutions than inheritance.
  • Separation of Concerns - Aligns with DRY by ensuring each concern is handled in one place, reducing duplication across the codebase.

Key Takeaways

DRY eliminates duplication of knowledge - if a bug fix needs to happen twice, extract the shared logic once

KISS prioritizes clarity over cleverness - optimize for readability, measure complexity in "how long to understand"

YAGNI defers speculative features - delay capabilities until real user or system pressure demands them

DRY vs. KISS: Don't extract at the cost of clarity - if a helper hides context, duplication might be kinder

KISS vs. YAGNI: Simple solutions are often easier to delete later - resist indirection until the system insists

DRY vs. YAGNI: Extract only after repetition proves intent - otherwise you create abstractions nobody needs yet

Track bugs: Were they caused by duplication, complexity, or premature abstraction?

Establish principle checks in design docs: "What evidence says we need this now?"

Keep exploring

Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.