YAGNI — You Aren't Gonna Need It
Don't implement something until you actually need it — speculative features add complexity today and may never deliver value.
★★★★★4/5Inside a codebase — classes, modules, files
How it works
YAGNI is an Extreme Programming (XP) principle, coined by Ron Jeffries around 1999. The rule: don't add functionality until it is genuinely needed for a current requirement.
The temptation to violate YAGNI is strong and feels responsible: 'I'll add a plugin system now so we're ready when we need it', 'I'll make this configurable so any team can use it', 'I'll build the abstraction now before the second use case arrives'. These all seem like good engineering. They are, in fact, speculation — you're paying a real cost today (complexity, maintenance, documentation, testing) for a hypothetical future benefit.
The problem is that the future rarely looks like you predicted. The plugin system you built handles the wrong plugin model. The configurability makes the simple case harder. The abstraction has the wrong seams for the second use case that actually arrives.
XP's answer: build exactly what the current user story needs. When the second use case arrives, refactor with full knowledge of both. The result is almost always a better abstraction than the one guessed in advance — and you only pay for it when you actually need it.
Implementation
TypeScript · Go · Rust// ❌ Speculative — building a plugin system "we might need later"
interface FormatterPlugin { transform(text: string): string; }
class TextFormatter {
private plugins: FormatterPlugin[] = [];
registerPlugin(p: FormatterPlugin) { this.plugins.push(p); }
format(text: string): string {
return this.plugins.reduce((t, p) => p.transform(t), text);
}
}
// Spent two days building this. Plugins: 0. Users who asked for it: 0.
// ✓ Build what you need now; refactor when the second use case arrives
function formatText(text: string): string {
return text.trim().replace(/\s+/g, " ");
}
// When a real second use case arrives, you'll know exactly what the
// abstraction should look like — and you'll build it with full knowledge.Why it matters
Unused functionality is not free — it must be maintained, documented, and understood by every developer who reads the code. Speculative generality makes current code harder and provides value that may never arrive.
✓ When to use
- →Any time you hear yourself say 'we might need this later'
- →When adding configuration options that no current feature requires
- →When building extension points before there are extensions to extend
- →When writing code to handle edge cases that don't yet exist in production
✗ When NOT to use
- →Security, compliance, and privacy — design these in from the start, never retrofit
- →Infrastructure decisions that are very expensive to change later (database choice, wire protocol)
- →Accessibility — it's much harder to add later than to design in from day one
Trade-offs
Less code to write, maintain, and understand today
Sometimes the cost of retrofitting really is higher than building it upfront
Abstractions built when both use cases exist are almost always better
Requires trust that refactoring is cheap — only true with good tests and team discipline
Avoids dead code that clutters the codebase indefinitely
Can be used to justify technical debt — 'we'll add error handling when we need it'
In production
37signals actively practices YAGNI — features are built for current customers, not hypothetical ones
GitHub launched with no enterprise features — added them as actual enterprise customers arrived with actual requirements
No network server, no user authentication — YAGNI-driven design; use cases that need those features use a different database
Industry adoption
Related principles
KISS — Keep It Simple
Most systems work best when kept as simple as possible — choose the straightforward solution over the clever one, and add complexity only when the problem demands it.
DRY — Don't Repeat Yourself
Every piece of knowledge should have a single, authoritative representation — duplication forces you to keep multiple copies in sync, and they inevitably drift.
Open/Closed Principle
Software entities should be open for extension but closed for modification — add new behaviour by writing new code, not by changing existing code.
Separation of Concerns
Divide a program into distinct sections where each section addresses one concern — so changes to one area don't ripple unexpectedly into unrelated areas.