You Are Going to Need It
YAGNI is an acronym for “you aren’t gonna need it.” It comes out of Extreme Programming (coined by Kent Beck), and in practice it’s another way of encouraging engineers to avoid premature optimization.
The trap it exists to catch is real. The classic failure mode for people who write code is to sit down and immediately assume you’re going to need every nice abstraction you can imagine. Everything has to be highly scalable and highly parallel out of the box. You can spend a lot of time making premature optimizations and premature abstractions in code that you probably won’t need. I understand that criticism, there’s a real history of engineers going in the opposite direction, being too idealistic about writing super proper code. And in a lot of cases, shipping as fast as possible to get feedback and find product market fit is exactly the right call.
But I think we’ve gone too far in the YAGNI direction, especially in the world of AI. I see it in a lot of the programmers I work with, and I’m watching the whole conversation re-emerge in AI coding circles in a form I find counterproductive (more on that below). I also think it works against the things that make a good engineer good.
The Load-Bearing Word Is “Gonna”
If you actually read Fowler’s writeup on YAGNI, he and Beck are more careful than the acronym’s popular usage. Beck’s formulation is to implement things when you actually need them, never because you foresee needing them. Fowler scopes the whole principle to presumptive features: capabilities you’re building on a guess. Which means YAGNI turns entirely on a prediction. It’s only a violation if you weren’t going to need it, and the acronym is (grammatically speaking) a confident forecast about the future.
So the interesting question, the one the acronym skips right past, is this: what’s your framework for knowing whether you’re going to need it?
That framework is wildly different depending on where you sit. If you’re bootstrapping a startup, you genuinely don’t know much yet (you may not even know whether you have customers), so lean into YAGNI and build only what you need to get feedback and chase product market fit. Totally cool. But if you’re like me and you work at a large company, the calculus shifts. I often already know a certain level of scale ahead of time (it’s a floor set by the size of the business, not a hope). I know the thing I’m building will be worked on by multiple engineers. When those facts are known, planning for them isn’t speculation. It’s just reading the requirements.
Fowler is also careful about scope in a way that gets dropped constantly in practice: YAGNI applies to capabilities built to support a presumptive feature, and it explicitly does not apply to effort spent making the software easier to modify. Notice which direction that carve-out runs. “We don’t need that abstraction yet” is almost always said about the modifiability work, which is the exact work Fowler exempts.
I don’t want to be pompous about this. But I do think there’s a lot to be said for spending a few cycles critically thinking about the proper abstractions and primitives you need for something you’re working on, in a way that makes your life (and the lives of the developers around you) easier. Part of engineering is being able to decompose difficult, complex problems into approachable chunks, and I think good engineers are anticipatory. That anticipation is a skill, not a vice.
Optimizations and Abstractions Are Different Sins
I want to be precise about where I’m pushing back, because I’m not defending premature everything.
Premature optimization? I’m mostly still with the consensus. Writing prematurely optimized code (the clever caching layer, the hand-tuning for load you don’t have) is unnecessary in a lot of cases, and profiling will tell you where the real bottlenecks are when you get there.
Premature abstraction is where I part ways, because I don’t think abstraction is the same kind of speculation. The cleanest code I’ve worked in is what I’d call atomically abstracted: everything is an input contract, some logic operating on that input, and an output contract describing what comes out. If you think of all your code as a series of those flows, it keeps your intuition about the system strong. Higher-level abstractions are how you maintain the intelligibility of that framework as you take on greater layers of complexity. A good abstraction isn’t speculative weight; it’s the thing keeping the system legible enough to change. And that loops right back to Fowler, who notes that YAGNI is only a viable strategy if the code is easy to change. Well-chosen abstractions are a large part of what makes code easy to change.
Sight Reading

Here’s the analogy I keep coming back to. I’m a musician, and I grew up as a band kid, so we did competitions. One of the things you do in those competitions is sight reading. For those not familiar: you and your group are handed a piece of sheet music, and you’re not allowed to practice it or work out your parts. You get a few minutes to read it, and then you’re expected to perform it on the fly and be judged on how musically you follow the piece.
I remember a seminar at what I believe was the Santa Cruz Jazz Festival where the instructor talked about the value of sight reading. He asked how many of us liked to play music, and we all raised our hands. He asked how much we liked playing in front of people, and we all raised our hands again. Then he asked how many of us loved to practice. Some hands went up. Not everyone’s.
His point was that sight reading makes you a better musician because it compresses the loop. It helps you learn pieces faster, and music in general faster, and it gets you to the point where you can pick up something new and perform it at performance level.
Now think about the arc of an engineering career. A lot of junior engineers are not great, and they can be slow. As you move into mid-level you get a little better and a little faster. And as you move up, there’s a matrix effect between how good your code is and how fast you can produce it. In my mind, the theoretical maximum engineer is someone who can take a problem at zero and write totally production-ready, scalable code on the first try, faster than anyone else. Nobody fully gets there, but that’s the direction the best engineers are heading, and by my definition good code includes the properly chosen abstractions that make your life easier as you build out whatever you’re working on.
I think the best engineers are sight reading their code. What I mean is that when they’re decomposing a problem, the question isn’t just can I play these notes. It isn’t even can I play the right notes. It’s can I play these notes musically, in a way that’s sonically pleasing. The same is true of code. The best engineers draw on their experience, their intuition, and their technical knowledge to pattern match: this element of the code is something it would be useful to abstract early, this other area probably not. They’re writing near-production code on the first pass, they’re doing it quickly, and they know when to make the trade between “you aren’t gonna need it” and “you probably are going to need it.”
Here’s the part of the analogy I care most about, though: YAGNI is, in some ways, the antithesis of flair. I can synthesize any note I want at the perfectly correct frequency, in perfect tuning, and stack those notes together. That doesn’t mean I’m making music. Music lives in the dynamics, the intensity, the space between the notes. Some of the most stylistic musicians are doing things slightly wrong on purpose. Pocket drummers sit just behind the beat to create that lazy, groovy feel; more driving drummers sit on top of the beat and push the song at an aggressive pace. Neither is metronome-perfect, and how you shift that placement changes the whole feel of the song. On a woodwind, the amount of air you give a note changes its entire character.
Code has the same margin, and I think that margin is where craft lives (I’d argue software engineering is both craft and science). A certain degree of elegance in how a system is decomposed is part of what makes great code great, the same way dynamics are part of what makes music music. If you train yourself under a hard YAGNI default, you’re practicing to a standard of “did I technically hit the notes,” and I think you fail to build some important muscles that way. Part of the muscle of choosing good abstractions is having paid the cost of choosing bad ones. AI has drastically lowered that tuition (more on this in a minute), which to me is an argument for practicing the skill more, not less.
Betting on Two Primitives

I’ll give a concrete example, and I’m going to keep the specifics somewhat generic.
I was recently working on a pipeline for an AI automation use case. We use Tangle at Shopify (which is open source), and as a side note from my past data engineering experience, I’ve been genuinely impressed with it. I’m not just shilling for my employer here. The content-based caching per task is really sweet, and it’s saved me the pain of building caching that I’ve otherwise had to build myself in Airflow or in custom solutions.
Tangle works on a component pattern (components get composed into pipelines), so one of the conversations we kept having as we started this new effort was how to build it. There was a real emphasis on speed. We wanted to move fast, and there was a healthy dose of YAGNI in those conversations.
I spent a lot of time thinking about what our core primitives actually were, and I landed on two.
The first was retrieving data from a data store in an abstracted way, so that you weren’t writing custom Python every time you needed data. You configure the component against your data store, configure a query, and get your data back as a Tangle manifest so it can be used in the pipeline.
The second was scaling a large number of agent tasks. We have some internal infrastructure that makes this easier, but I wanted a heavily abstracted component around how we fan work out to a large number of agent sessions.
The bet was that spending a couple of extra cycles making these primitives easy to use would be an accelerant, because we already knew out of the gate that we were going to build a series of pipelines to support this automation effort. This was never going to be one pipeline.
Then we had to make an infrastructure pivot. We had assumed one internal solution and moved to a different one, and it turned out the new one had a capability we hadn’t been aware of when we designed the highly parallel agent session architecture. Because we already had that abstracted component, I was able to build a POC extremely quickly, and then we were able to scale on top of it just as quickly. Scaling had been the hard part with the original architecture. It was straining at a few dozen concurrent tasks. We’re currently testing at a scale roughly thirty times that.
That’s the payoff I was after. Rather than looking for the shortest path to getting something out, I did a bit of critical thinking and problem solving up front to engineer something that’s scalable and elegant for extension.
And the anticipation wasn’t really a guess. Over the last few years I’ve built a number of architectures shaped like this: how do I churn through a bunch of data using agents, reliably and at scale? Drawing on that experience, I recognized that the pattern wasn’t unique to this one pipeline, and honestly it wasn’t unique to this project either. Take data, feed it to a large number of parallel agents that operate over that data, collect the results, then do something with the results. That’s a pattern worth accelerating.
I had some real discussions with other engineers about whether this was premature optimization, and I felt convicted that it wasn’t (based on my experience and on what we knew we had coming). I think it’s already paid off. To be clear, I don’t think this example refutes Fowler. I think Fowler would have signed off on it, because I knew this was never going to be one pipeline. The investment wasn’t a presumptive feature; it was the roadmap. The reason I bring it up is that the YAGNI I hear in real conversations would have talked me out of it anyway.
The YAGNI I Actually Encounter
That’s the gap I keep running into: the theoretical version of YAGNI and the applied version are not the same thing. In the wild, YAGNI mostly shows up as “write the absolutely most basic code for the first thing, and we’ll find out if we need more.” That works exactly as designed if you actually get the follow-on cycles, because YAGNI is allied to agile. The whole model presumes your abstractions will keep changing as the business requirements change, and that you’ll be there refactoring when they do.
I’ve worked on enough projects to know that presumption often doesn’t hold. Somebody has a business requirement, you implement the requirement, and then they move on (usually to a completely different thing). You work on something for a sprint or two, it ships to production, and the organization has no appetite for spending cycles heavily iterating the base logic. That only happens when it absolutely has to. There is no backlog ticket six sprints out to go correct your abstractions. The code you shipped is the code somebody maintains, and extends, for years.
In that world, I’d rather invest a little more time up front to get the abstractions right and minimize the downstream iteration I know is never going to be funded. I get that this is a little anti-agile, and maybe I’m over-indexing on the scenarios I’ve personally lived through. But if the actual deal is one or two sprints and then everyone moves on, “wait until you need it” is a bad trade, because the moment when you’d act on the need never arrives.
Prompting Against the Grain

Which brings me to AI, because AI is why this debate re-ignited, and it’s where I think the misapplication is sharpest.
If you’ve spent time with coding agents, you know they tend to write verbose and often over-abstracted code by default. A whole genre of tooling has emerged to fight that. The most viral example is Ponytail, the agent skill that promises to make your model “reason like the laziest senior developer in the room” (it picked up over 44,000 GitHub stars in nine days, though its headline claim of 80 to 94 percent less code was later revised down to 54 percent after a contributor challenged the benchmark). It’s YAGNI, operationalized as a prompt.
I understand the appeal. But step back and look at what we’re doing: these models are tuned to write abstracted code, and we’re burning effort prompting them out of their strength instead of leaning into it. My argument is that we should consider the opposite. The model is good at writing abstractions, so I want to be the one deciding which abstractions it writes. I drive the decomposition, I’m opinionated about the primitives and the contracts, and the agent implements them (which it’s genuinely good at). And the speed of AI iteration means that even when one of my abstractions turns out to be premature, the cost of unwinding it is a fraction of what it used to be.
That last point is worth making precise, because Fowler’s case for YAGNI is an economic one. He names the costs of a presumptive feature: the cost of build, the cost of delay, the cost of carry, and the cost of repair. Now look at what AI has done to that ledger. The cost of build has collapsed. The cost of repair has shrunk enormously (refactoring with an agent is fast and cheap). The cost of delay barely applies when the abstraction takes an afternoon instead of a month. What’s left is the cost of carry, and a well-chosen abstraction’s carrying cost is low. That’s what makes it well-chosen. The costs YAGNI guards against are exactly the costs AI has reduced the most. The principle isn’t wrong; the economics changed.
So if the cost of a better abstraction is you spending a few brain cycles specifying it more precisely to your agent, the trade-off is far, far cheaper than it’s ever been. This is why I think there’s actually less of an excuse for writing low-quality code now than there was before (which sounds crazy in a world where people are lobbing AI slop grenades at each other). Because AI lets you iterate so fast, and because it does a pretty good job of implementing your ideas, it affords you more space to think critically about the systems you’re building and the code you’re writing.
It just requires a little more self-discipline to actually use that space, rather than pulling the roulette wheel of the agent (throwing things at it and hoping good output comes back). The way I look at it, the agent helps with implementation and it helps with discovery, especially working through existing codebases or unfamiliar frameworks. The thinking about what to build, and what shape it should take, is still mine.
I’ve written before that we infantilized programmers. For those of us who consider ourselves software engineers, who believe in applying real engineering principles to the work and holding ourselves to that standard, I think falling too far into YAGNI is dangerous and counterproductive.
Where I Land
As with everything I write, this is just my perspective. It may change over time, and I may be wrong. That’s fine with me, and I’m open to the alternative.
I also don’t have a clean rule for when to abstract and when not to, and I don’t think one exists (cough rule of three cough). That’s part of why “you aren’t gonna need it” is so appealing as a default (it’s a rule, and rules are easier than judgment). What I’d push back on is treating it as the thing you do until proven otherwise, which is the framework I think a lot of engineers, and now a lot of agent skills, are operating under.
There’s something to be said for the art of picking good abstractions and writing high-quality code, and a reflexive YAGNI can be detrimental to developing the engineer who is both good and fast. It’s the equivalent of playing the notes with no dynamics and no flair. The notes are right. It just isn’t music.