DEV Community

Cover image for When Duplicate Code Is the Better Design

When Duplicate Code Is the Better Design

Adam - The Developer on June 01, 2026

You've seen the developer. Maybe you are the developer. They discover DRY — ✨ Don't Repeat Yourself ✨ — and something switches in their brain. A p...
Collapse
 
tobi_augenstein profile image
Tobias Augenstein

Thanks for this balanced perspective. One aspect I'd like to add is that unifying implementation doesn't necessarily mean to also unify the API. For the name formatting example you could do:

function formatUserName(user) {
  return formatName(user);
}
Enter fullscreen mode Exit fullscreen mode

Avoids duplication and is still easy to change, e.g.:

function formatUserName(user) {
  const name = formatName(user);
  return `${name} (${user.username})`;
}
Enter fullscreen mode Exit fullscreen mode

Usually it's not all or nothing. I think it's often best to try find a middle ground - avoiding duplication of logic as much as reasonable possible without trying to create the ultimate abstraction.

Collapse
 
adamthedeveloper profile image
Adam - The Developer • Edited

Yeah I get what you mean, and I think that’s the healthy version of DRY most people eventually land on.

The problem usually isn’t sharing code, it’s when we start forcing shared structure where only part of it is actually shared. That’s when APIs get split from implementations and everything starts feeling indirect.

Your example is actually a good one because it keeps the API clear while still reusing the core logic. That’s the sweet spot.

Where I usually get cautious is when it starts looking like this:

function getActiveUsers() {
  return getEntities('users', { isActive: true });
}

function getActiveOrders() {
  return getEntities('orders', { status: 'active' });
}
Enter fullscreen mode Exit fullscreen mode

At first it looks fine, but then every domain starts bending around this generic “getEntities” shape, and suddenly the abstraction is dictating how features are expressed instead of the other way around.

I’m fine with reuse when it’s just “this is the same logic, let’s not duplicate it.” But I try to avoid turning that into a framework layer unless I’ve actually seen the differences emerge in practice.

Collapse
 
johnnylemonny profile image
𝗝𝗼𝗵𝗻

Great read - a thoughtful reminder that sometimes intentional duplication improves clarity and reduces risky coupling. The practical examples made the trade-offs clear; thanks for challenging the DRY-first mindset.

Collapse
 
adamthedeveloper profile image
Adam - The Developer

Appreciate that ⭐️

Yeah that’s pretty much it — once you’ve been burned a couple times by “smart” abstractions, you stop trusting similarity as a reason to unify things 😄

Collapse
 
rondo profile image
Rondo

Great insight! As a junior dev, I tend to try to abstract functions looking similar. But.. It mostly turned out to be increase of complexity😂
And of course, I also think duplication is better than too much abstraction. Duplicated code is normally, at least, easy to understand. But too-much-abstracted code is hard to even read.

Collapse
 
adamthedeveloper profile image
Adam - The Developer

Haha, same story for a lot of us 😆

I think most developers go through a phase where we discover DRY and start abstracting everything that looks remotely similar. Then a few months later we're adding more parameters, more conditionals, and more overrides than the duplicate code ever had.

I've found that duplicate code is usually easier to deal with than a wrong abstraction. At least the duplication is obvious and doesn't force unrelated things to evolve together.

Collapse
 
kyej_dev profile image
Kye Jones

This is such a good breakdown. DRY is one of those principles that sounds simple until you realise the wrong abstraction can be way more painful than a little duplication. The “duplicate knowledge vs duplicate shape” point is huge. Do you usually wait until the third repeated pattern before abstracting?

Collapse
 
adamthedeveloper profile image
Adam - The Developer

Thanks! And yeah, I generally follow the "third time" rule. The first time, I just write it. The second time, I notice the pattern. By the third time, I usually have enough examples to tell whether it's actually the same knowledge being repeated or just similar-looking code.

I've been burned more times by abstracting too early than by leaving a bit of duplication around for a while. It's surprisingly common for two things to start out looking identical and then head in completely different directions a few months later 😅

Collapse
 
cryptovibeapp profile image
CryptoVibe

The sendNotification example is so real. I've inherited that exact function before, except it had grown to take a config object because someone got tired of adding parameters. At that point you're not really sharing logic anymore, you're just hiding the fact that these are three different things.

The "wait for the third time" rule is good but I'd add: even when you do abstract, check if the things you're unifying actually change together. If email and SMS notifications evolve independently, keeping them separate isn't duplication, it's just accurate modeling.

Collapse
 
adamthedeveloper profile image
Adam - The Developer

Yeah, that’s exactly the failure mode, once a function turns into a “config object sink”, it usually means we’ve stopped modeling anything and just started hiding complexity behind a single entry point.

I really like your addition too. The “change together” check is honestly the part that matters more than the “third time” rule. Repetition alone isn’t enough — what really tells you whether abstraction makes sense is whether the reasons for change are actually shared.

If email and SMS evolve for different business reasons, keeping them separate isn’t duplication at all, it’s just honest modeling of reality. Trying to force them together usually just delays the pain.

Collapse
 
rynux profile image
Rynux

Good, it's long text but it's very usefull. Thanks