Every developer has heard it: "Don't Repeat Yourself." It's one of the first principles we learn, and it sounds so reasonable that we rarely question it. See duplicate code? Extract it. See a pattern? Abstract it. Three similar lines? Time for a helper function.
But after 16 years of building software across industries and countries — from e-learning platforms in Rome to financial trading systems in Zurich — I've come to a different conclusion: YAGNI — "You Aren't Gonna Need It" — is the more valuable principle, and blind adherence to DRY actively harms codebases.
The DRY Trap
DRY was introduced by Andy Hunt and Dave Thomas in "The Pragmatic Programmer." Their original definition was about knowledge: "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." Notice the word: knowledge, not code.
What most developers practice isn't DRY — it's "Don't Repeat Code," which is a fundamentally different thing. Two pieces of code can look identical today but represent entirely different business concepts that will diverge tomorrow.
I've seen this play out many times: validation logic for two different flows looks identical at first. A well-intentioned developer merges them into a shared utility. Months later, regulatory changes or product decisions require different rules for each flow, and untangling that shared utility means touching every caller, rewriting tests, and coordinating deployments across teams.
// These look the same, but they're NOT the same knowledge
function validateUserEmail(email: string) {
return email.includes('@') && email.length > 5;
}
function validateNewsletterEmail(email: string) {
return email.includes('@') && email.length > 5;
}
// Merging them into validateEmail() creates coupling
// between user registration and newsletter signup.
// When newsletter needs to accept '+' aliases but
// registration doesn't, you'll fight the abstraction.Why YAGNI Wins
YAGNI says: don't build it until you need it. Don't abstract until you've seen the real pattern. Don't generalize until you understand the specific cases. This isn't laziness — it's discipline.
The cost of wrong abstraction is much higher than the cost of duplication. Duplication is obvious and local — you can see it, and fixing it is straightforward. A wrong abstraction is subtle and viral — it shapes how people think about the code, and unwinding it means refactoring every caller.
I follow the "Rule of Three": don't abstract until you've seen three genuinely independent uses. And even then, ask yourself whether the duplication represents the same knowledge or just similar-looking code.
At Migros, when we rebuilt Bikeworld and Micasa on a shared Next.js monorepo, the temptation to share everything between stores was enormous. But product pages for bikes and furniture have fundamentally different user journeys. We kept components separate until real shared patterns emerged organically — and the result was a codebase where each store could evolve independently without coordinating releases.
The Real Cost of Premature Abstraction
I've seen this pattern play out dozens of times in teams I've led — from small agencies in Italy to enterprise teams at AXA and UBS. Someone creates a "shared" component or utility early on. It works great for two use cases. Then a third comes along that almost fits but needs a flag. Then a fourth needs another flag. Soon you have a function with six boolean parameters, and nobody can reason about what it actually does.
This is what I call "abstraction debt." Unlike technical debt, which is visible in your build times and dependency lists, abstraction debt hides in your team's velocity. Developers slow down because they're afraid to modify shared code. New features take longer because they have to work around abstractions designed for different use cases.
// The abstraction graveyard — we've all seen this
function formatData(
data: unknown,
isUser?: boolean,
isAdmin?: boolean,
includeMetadata?: boolean,
skipValidation?: boolean,
legacyMode?: boolean
) {
// 200 lines of branching logic
// that nobody dares to touch
}YAGNI in Practice: Real-World Signals
Here's how I apply YAGNI in day-to-day decisions:
When building the Vontobel Markets platform, we needed real-time price updates via WebSockets. The initial instinct was to build a generic real-time data layer that could handle any future streaming use case. Instead, we built exactly what the price ticker needed — nothing more. When a second real-time feature came along (portfolio alerts), we understood both use cases well enough to design something that genuinely served both, rather than guessing upfront.
At UBS, when unifying the Administrative Officers application, I actively discouraged creating a "universal form framework" that would handle every form in the system. Instead, each team built their forms to their own needs, and we identified truly shared patterns only after several teams had shipped. The patterns we eventually extracted were simpler and more robust than anything we could have designed upfront.
What I Tell My Teams
When I onboard developers, I share this mental model: duplication is far cheaper than the wrong abstraction. Copy-paste those three lines. Let the pattern emerge naturally. When you've seen the same concept — not the same code, the same concept — appear three or more times, then consider whether an abstraction would clarify or obscure.
"Make sense" is one of my core principles. Every decision should make sense in context, not just follow a rule. DRY makes sense when you're genuinely encoding shared knowledge. YAGNI makes sense almost everywhere else.
The best code I've shipped wasn't the most clever or the most DRY. It was the code where each piece clearly expressed its intent, where you could change one thing without worrying about breaking five others, and where new team members could understand it without a guided tour.
The next time you reach for a shared utility, pause. Ask: "Am I abstracting because I see a real pattern, or because I've been told repetition is bad?" That pause is worth more than any design pattern.

