You Aren’t Gonna Need It (YAGNI)
Status: Complete
Category: Clean Code
Default enforcement: Advisory
Author: PushBackLog team
Tags
- Topic: quality, supportability
- Methodology: YAGNI
- Skillset: any
- Technology: generic
- Stage: refinement, execution
Summary
Do not add functionality until it is actually needed. Building features, abstractions, or infrastructure for hypothetical future requirements wastes effort, adds complexity, and often turns out to be wrong when the real requirement eventually arrives.
Rationale
Prediction is hard and expensive
Engineers are optimistic about predicting future needs and pessimistic about predicting future cost. The typical internal monologue is: “this will be easy to add now while I’m in this part of the code” and “it’ll be much harder to add later”. Both predictions are usually wrong. Future requirements arrive with different shapes than anticipated; premature infrastructure is either unused or forces awkward adaptation. The cost is absorbed twice: once when building it, once when working around it.
Ron Jeffries coined YAGNI as an Extreme Programming principle: build exactly what the current story requires. If a future story needs something different, you’ll have more information then than you do now — information that will result in a better design.
The carrying cost of speculative features
Every piece of code has a carrying cost: it must be read, understood, tested, and maintained indefinitely. Speculative code carries this cost from day one with zero return until (if ever) it becomes necessary. Worse, it becomes context that new team members must understand, and it creates coupling that constraints future design choices.
YAGNI and the AI multiplier
AI coding tools are exceptionally good at building things you describe. They are less good at determining whether you should build them at all. YAGNI is a check on the AI’s tendency to scaffold elaborate solutions for hypothetical requirements. “Add a plugin system so others can extend this later” is a YAGNI violation whether a human or an AI proposes it.
Guidance
The YAGNI test
Before implementing any feature, configuration option, or abstraction, answer:
- Is there a story in the current sprint that requires this?
- Do you have a named, confirmed user who needs this capability today?
- If you skip this, what specifically breaks right now?
If all three answers are “no”, don’t build it. Add a ticket for future evaluation if the requirement seems plausible.
YAGNI vs technical investment
YAGNI does not mean zero forward planning. There is a category of decisions — architectural choices, schema design, data model structure — where later change is genuinely expensive and some foresight is warranted. The distinction:
| YAGNI applies | YAGNI does not apply |
|---|---|
| Feature no one has asked for | Schema that’s hard to migrate later |
| Configuration hooks for hypothetical cloud providers | Choosing a message queue to decouple services that will demonstrably scale |
| Plugin API no consumer exists for | Pagination in an API that will almost certainly be called with large datasets |
| Test doubles for dependencies that don’t exist | Auth token design (too costly to change later) |
The heuristic: if the cost of the wrong design is reversible in a day’s work, YAGNI applies. If the cost is a multi-week migration, some foresight is warranted.
Examples
Classic YAGNI violation: the internationalisation trap
// Before YAGNI awareness
class UserGreeting {
getMessage(userId: string, locale: string = 'en-US'): string {
const translations = {
'en-US': 'Welcome back!',
'fr-FR': 'Bienvenue !', // No French users exist yet
'de-DE': 'Willkommen!', // No German users exist yet
};
return translations[locale] ?? translations['en-US'];
}
}
The translation infrastructure is built for locales no user has requested. When internationalisation is actually added, the shape of the requirement (use a proper i18n library, load translations from a CDN, support RTL) bears no resemblance to this stub, and the stub must be removed anyway.
YAGNI-compliant version
class UserGreeting {
getMessage(): string {
return 'Welcome back!';
}
}
When internationalisation becomes a real story: replace with the correct implementation.
Anti-patterns
1. “Just in case” configuration
Adding environment variables, feature flags, and config file keys that no deployed environment currently uses. Each unused config key must be documented, understood, and maintained.
2. Plugin/extension systems for the first use case
Building a plugin architecture when there is exactly one plugin. The first use case almost never reveals the correct shape of the extension API.
3. Generalising before there are two cases
Extracting a configurable abstraction from a single concrete implementation. If there is only one caller, the abstraction is theoretical.
4. “This will be needed later”
This statement is almost never grounded in a confirmed requirement. Unless “later” is “next sprint” and the story is written, it’s speculation.
5. Infrastructure for scale that doesn’t exist
Message queues, caching layers, distributed systems for an application with 12 users. Optimise for current load or load you can credibly project. Premature scaling infrastructure is YAGNI at the architecture level.
Related practices
Part of the PushBackLog Best Practices Library. Suggest improvements →