YAGNI: The Feature You're Building Right Now Is Probably Waste

Published: at 09:00 AM

You are, right now, probably building something nobody asked for.

Not the ticket. The extra config option ‘in case we need it later.’ The abstract base class for a subclass that doesn’t exist yet. The feature flag for a feature that isn’t finished. The plugin system for a product with one plugin. It feels responsible. It feels like planning ahead. Most of the time, it’s just waste with better posture.

There’s a name for this, and it’s older than most of the frameworks you’re using today.

YAGNI: You Aren’t Gonna Need It

YAGNI comes out of extreme programming, and Ron Jeffries, one of its co-founders, put it about as plainly as it can be put: always implement things when you actually need them, never when you just foresee that you’ll need them (Wikipedia).

That’s the whole principle. Don’t build for the future you’re imagining. Build for the requirement in front of you, and trust that you can add the next piece when the next piece is actually real.

It sounds almost too simple to be useful, until you notice how much of your own codebase violates it. The ‘flexible’ config system nobody has ever needed a second value for. The interface with exactly one implementation, forever. The parameter that’s always called with the same value because the ‘someday’ use case never showed up.

Why ‘Foreseeing’ Is a Trap

The uncomfortable part of YAGNI isn’t the rule, it’s admitting that foresight in software is usually wrong. You’re not bad at predicting the future. Everyone is bad at predicting the future. The requirement you build early for often changes shape by the time it’s real, and now you’re maintaining a speculative abstraction that doesn’t even fit the thing it was built for.

Worse, that unused flexibility isn’t free while it waits. It’s code that has to be read, tested, and reasoned about by everyone who touches the file after you. Every ‘just in case’ branch is a tax paid by every future contributor, including future you, whether or not the case ever arrives.

YAGNI Doesn’t Work Alone

Here’s the part that gets left out when people quote YAGNI as an excuse to skip design entirely: the Wikipedia entry is explicit that YAGNI depends on other practices to actually be safe. Specifically, continuous refactoring, automated unit testing, and continuous integration. Without those, ‘build only what you need now’ can just as easily produce a tangled mess, because you never circle back to clean up what the next requirement demands.

YAGNI isn’t ‘don’t think ahead.’ It’s ‘don’t build ahead, but keep the code cheap enough to change that building ahead was never necessary.’ The safety net is what makes the principle honest instead of an excuse for sloppiness.

The Family YAGNI Belongs To

YAGNI rarely shows up alone. It’s part of a small cluster of principles that all point at the same thing from different angles.

KISS, keep it simple. The oldest and broadest of the group. Where YAGNI targets unneeded features, KISS targets unneeded complexity in general, simple solutions over clever ones, even for problems you do need to solve today.

DRY, don’t repeat yourself. Often mistaken as YAGNI’s opposite (surely deduplicating means adding more structure), but they agree more than they conflict. DRY says don’t maintain the same logic in two places. It doesn’t say build the abstraction before you have two places. Premature DRY, abstracting after one use case instead of two or three, is its own kind of YAGNI violation.

Avoiding premature optimization. Donald Knuth’s line that premature optimization is the root of all evil is the same argument applied to performance instead of features: don’t pay complexity cost for a problem you don’t have measured evidence of yet.

The minimum viable product. The product-level version of YAGNI. Ship the smallest thing that tests the real assumption, not the fully-loaded version you can imagine wanting eventually.

Overengineering, the failure mode all of the above are guarding against. Every one of these principles exists because overengineering is the default outcome of unmanaged good intentions. Nobody sets out to overbuild. It happens one reasonable-sounding ‘what if’ at a time.

What This Looks Like on a Real Ticket

Next time you catch yourself adding an option, a hook, or a layer of abstraction that the current ticket doesn’t strictly require, ask one question: is this needed for what I’m shipping today, or am I building it because I can picture a future where it might matter?

If it’s the second one, don’t build it. Write the code that solves today’s problem as plainly as possible, keep it well tested so it’s cheap to change, and let the next real requirement tell you what the next real abstraction should look like. That’s not a lack of ambition. It’s the entire discipline.