YAGNI

YAGNI

YAGNI — You Aren't Gonna Need It — means: do not build a feature until you have a real, current need for it. It protects against speculative complexity: abstractions, extension points, and configuration options added for hypothetical future requirements that never arrive. Every line of code you write has a maintenance cost; YAGNI says don't pay that cost for features you don't need yet. A team spent 3 sprints building a plugin architecture for their notification system "in case we need to support SMS, push, and Slack" — 18 months later, only email was used, and the plugin abstraction layer had accumulated 40+ unit tests, a factory, a registry, and a configuration schema that all needed maintenance for a single implementation.

YAGNI is most useful when design discussions drift toward "what if we need to support X later?" — the answer is: build it when X is a real requirement with a real user, not before.

When to Apply It

The signal that YAGNI applies: a design decision is driven by a hypothetical future requirement rather than a current one. Common patterns:

The counter-signal: YAGNI does not mean "never design for change." It means don't pay the cost of flexibility until the need is real. Reversible decisions (feature flags, thin adapters, config values) are cheap — build them. Irreversible decisions (public API contracts, database schemas, wire protocols) deserve more upfront thought because changing them later is expensive.

Example

// YAGNI violation: generic plugin system for a feature with one implementation
public interface IReportGenerator { Report Generate(ReportRequest req); }
public class PdfReportGenerator : IReportGenerator { ... }
public class ReportGeneratorFactory
{
    public IReportGenerator Create(string type) => type switch
    {
        "pdf" => new PdfReportGenerator(),
        _     => throw new NotSupportedException(type)
    };
}
// There is no second report type. The factory and interface add complexity for no current value.

// YAGNI-compliant: direct implementation until a second type is needed
public class ReportService
{
    public Report Generate(ReportRequest req) { /* PDF logic directly */ }
}
// When a second format is needed, introduce the abstraction then — with a real use case to guide the design.

A second common YAGNI violation is building a generic configuration system for a feature that has exactly one configuration today:

// YAGNI violation: generic config system for one setting
public class FeatureConfig
{
    public Dictionary<string, object> Settings { get; set; } = new();
    public T Get<T>(string key) => (T)Settings[key];
}

// YAGNI-compliant: direct property until a second setting is needed
public class FeatureConfig
{
    public bool IsEnabled { get; set; }
}
// When a second setting arrives, refactor then — with a real use case to guide the design.

Pitfalls

Speculative Generality

What goes wrong: a class or method is made generic to handle cases that don't exist yet. The abstraction is harder to understand, harder to test, and often wrong when the hypothetical case finally arrives — because the real requirements differ from what was imagined. A team built a generic IDocumentProcessor<TDoc, TResult> pipeline for PDF generation; when CSV export was finally needed 14 months later, the PDF-oriented interface assumptions (page breaks, fonts, margins) were so baked into the abstraction that the team had to bypass it entirely and write CSV export from scratch, leaving the generic pipeline as dead code that only PDFs used.

Why it happens: developers anticipate future needs and add flexibility 'for free' while they're in the code. The cost is paid in complexity, not in time.

Mitigation: apply the Rule of Three — introduce an abstraction when you have two concrete use cases, not one. One use case is speculation; two give you enough information to design the abstraction correctly.

Tradeoffs

YAGNI vs forward-thinking design

Dimension YAGNI (build now) Speculative design (build ahead)
Cost if needed Refactor later (usually cheap) Paid upfront (certain)
Cost if not needed Zero Maintenance burden forever
Design quality May need refactoring when need arrives May be wrong when need arrives
Reversibility High (add abstraction when needed) Low (removing abstraction is hard)

Decision rule: apply YAGNI by default. The exception is when the cost of adding the abstraction later is genuinely high — public API contracts, database schemas, wire protocols, or security boundaries. For these, upfront design is justified because changing them later is expensive or breaking. For internal code, add the abstraction when you have two concrete use cases, not one.

Questions

References


Whats next