← Back to principles

Design Principle

YAGNI — You Aren't Gonna Need It

Invest in capabilities when evidence demands them, not when imagination suggests them.

YAGNI is a brake pedal for enthusiastic architects. It reminds you to wait until the system insists before building abstractions or scaling features.


1. Delay generalisation

The future will surprise you. Resist generic solutions until you hold at least two concrete use cases.

Keep it concrete until variation arrivesTS
1// Premature: full plug-in system with reflection.
2type Plugin = {
3name: string;
4execute(): Promise<void>;
5};
6
7const registry: Record<string, Plugin> = {};
8
9export function register(plugin: Plugin) {
10registry[plugin.name] = plugin;
11}
12
13// Practical: event hooks around today's workflow.
14type InvoiceHooks = {
15beforeCreate?: (invoice: InvoiceDraft) => Promise<void>;
16afterCreate?: (invoice: Invoice) => Promise<void>;
17};
18
19export function createInvoiceService(hooks: InvoiceHooks = {}) {
20return {
21 async create(draft: InvoiceDraft) {
22 await hooks.beforeCreate?.(draft);
23 const invoice = await persist(draft);
24 await hooks.afterCreate?.(invoice);
25 return invoice;
26 },
27};
28}

2. Scope to the real request

Capture product requirements in concrete language. If the story doesn’t demand multi-tenancy, don’t design for it.

1// Ticket: "Add dark mode toggle to settings".
2// ❌ Overbuilt solution: theme marketplace with persistence, previews, and sandboxing.
3
4// ✅ Start with the toggle the user asked for.
5export function ThemeToggle() {
6const { theme, setTheme } = useTheme();
7const isDark = theme === "dark";
8
9return (
10 <button onClick={() => setTheme(isDark ? "light" : "dark")}>
11 {isDark ? "Switch to light" : "Switch to dark"}
12 </button>
13);
14}

3. Document the decision

Saying “not yet” is easier when you record why. Log the assumption so you can revisit when constraints change.

1## Decision: Single Region Deployment
2
3- **Date:** 2025-02-15
4- **Context:** Only North America traffic. P99 response 90ms.
5- **Decision:** Stay single-region until latency > 150ms or EU traffic > 20%.
6- **Follow-up:** Re-evaluate quarterly.

How to practice YAGNI

  1. Write a test that describes the real user journey. If the test doesn’t fail, you probably don’t need the feature yet.
  2. Budget future work. Estimate cost to add the capability later; if it’s cheap, defer it.
  3. Celebrate deletion. Make pruning dead branches part of your sprint ritual.

Reflection

Identify a module you designed “just in case.” What is its actual usage? How many times has it saved you? If the answer is never, schedule a clean-up.

Takeaway: YAGNI guards energy for real needs. Defer gracefully, document assumptions, and build when evidence demands it.


Interview Questions

Beginner

Q: What does YAGNI stand for and why is it important?

A: YAGNI stands for "You Aren't Gonna Need It." It's a principle that reminds developers to avoid building features or abstractions until there's actual evidence they're needed. It guards against premature optimization and over-engineering. The idea is to solve today's problems with today's solutions, and defer speculative features until real user or system pressure demands them.


Intermediate

Q: Give an example of violating YAGNI. How would you apply YAGNI instead?

A:

Violation: Building a full multi-provider storage abstraction when you only use S3:

abstract class StorageProvider {
  abstract put(key: string, value: string): Promise<void>;
}

class S3Storage extends StorageProvider { /* ... */ }
class GCSStorage extends StorageProvider { /* ... */ }
class AzureStorage extends StorageProvider { /* ... */ }

// But you only ever use S3Storage

YAGNI approach: Solve today's need, keep the seam obvious:

type Storage = {
  put(key: string, value: string): Promise<void>;
};

const storage: Storage = {
  async put(key, value) {
    await s3Client.putObject({ key, value });
  },
};

// If you later need GCS, you can swap the implementation
// The interface is simple enough to change without major refactoring

Key insight: The simple version is easy to change later. If you never need multiple providers, you saved time. If you do need them, the refactor is straightforward because the interface is already clean.


Senior

Q: You're building a social media platform. How do you apply YAGNI when designing the architecture? What would you build in v1 vs. defer? How do you make it easy to add deferred features later?

A:

V1 (YAGNI - build only what's needed now):

  • Single-region deployment (US only)
  • Monolithic service (or 2-3 services max: API, feed, notifications)
  • PostgreSQL for all data (users, posts, comments, likes)
  • Simple feed: SELECT * FROM posts ORDER BY created_at DESC LIMIT 50
  • In-memory caching (Redis) for session management only
  • Synchronous notifications (send email immediately)
  • No real-time features (polling for new posts)

Defer until evidence demands:

  • Multi-region/CDN (wait until you have international users complaining about latency)
  • Microservices (wait until team is >10 people or services have different scaling needs)
  • Specialized databases (wait until PostgreSQL becomes bottleneck - maybe never)
  • Complex feed algorithms (wait until users complain about relevance)
  • Distributed caching strategy (wait until database is slow)
  • Message queue for async notifications (wait until email sending blocks requests)
  • WebSockets/real-time (wait until users request live updates)

Making it easy to add later:

  1. Keep seams obvious:

    // V1: Simple function
    async function getFeed(userId: string): Promise<Post[]> {
      return db.query("SELECT * FROM posts ORDER BY created_at DESC LIMIT 50");
    }
    
    // Easy to evolve to:
    async function getFeed(userId: string): Promise<Post[]> {
      return feedAlgorithm.getPersonalizedFeed(userId); // Swap implementation
    }
    
  2. Document assumptions:

    ## Decision: Single-Region Deployment
    - Date: 2025-01-15
    - Context: 100% US traffic, P99 latency 90ms
    - Decision: Stay single-region until latency > 150ms or EU traffic > 20%
    - Follow-up: Re-evaluate quarterly
    
  3. Use interfaces, not concrete classes:

    // V1: Simple implementation
    interface NotificationService {
      send(userId: string, message: string): Promise<void>;
    }
    
    class SimpleNotificationService implements NotificationService {
      async send(userId, message) {
        await email.send(userId, message); // Synchronous, simple
      }
    }
    
    // Later: Swap to async without changing callers
    class AsyncNotificationService implements NotificationService {
      async send(userId, message) {
        await queue.publish({ userId, message }); // Async, scalable
      }
    }
    
  4. Budget future work: Estimate cost to add later. If it's cheap (like swapping an interface implementation), defer it confidently.

Key insight: YAGNI doesn't mean "never plan." It means "build the simplest thing that works, document why, and make it easy to evolve when evidence demands it." The simple v1 design should have obvious extension points.


Key Takeaways

  • Delay generalization - resist generic solutions until you have at least two concrete use cases
  • Scope to the real request - capture requirements in concrete language; if the story doesn't demand it, don't design for it
  • Document the decision - saying "not yet" is easier when you record why; log assumptions to revisit when constraints change
  • Write tests that describe real user journeys - if the test doesn't fail, you probably don't need the feature yet
  • Budget future work - estimate cost to add capability later; if it's cheap, defer it confidently
  • Celebrate deletion - make pruning dead branches part of your sprint ritual
  • Keep seams obvious - simple designs should have clear extension points for when evidence demands evolution
  • Use interfaces, not concrete classes - makes swapping implementations easy without changing callers
  • YAGNI guards energy for real needs - defer gracefully, document assumptions, build when evidence demands it

Keep exploring

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