DEV Community

Petr Tcoi
Petr Tcoi

Posted on

When You Should “Wet” Your Code: Why Blindly Following DRY Can Hurt Your Project

The DRY principle (Don’t Repeat Yourself) is probably one of the first rules we learn as developers. Its idea seems flawless: don’t repeat code, don’t duplicate knowledge, and move everything that repeats into a single place.

In practice, this means that if a piece of logic appears in multiple places, it should be centralized - as a function, class, module, or component.

This makes the project cleaner, easier to maintain, and safer to modify. Fix a bug in one place - and it disappears everywhere.

In business logic, especially on the backend, DRY works beautifully. Data validation, discount calculations, and authorization rules are perfect examples of knowledge that must be centralized. When such logic is scattered across different services, inconsistencies and bugs are inevitable. In these cases, DRY can literally save months of work, make the system predictable, and simplify refactoring.

But my personal experience - and I mostly work with startups and fast-changing frontend projects — has shown that this principle, when applied dogmatically, can do real damage. When requirements change every week and design lives its own life, strict DRY often becomes a development bottleneck rather than a path to clean code. The more “mature” a project becomes, the “drier” it can be. But in the early stages, too much dryness can be fatal.

DRY in Theory: The Original Idea and Its Strengths

DRY wasn’t created as a fight against repeating code just for aesthetics - it was a fight against repeating knowledge. The authors of the principle, Andrew Hunt and David Thomas, explained in The Pragmatic Programmer that if the same business rule appears in multiple places, it’s a sign of an architectural flaw.

The core idea is simple:

  • Every piece of knowledge should exist in one and only one place.
  • Changing that knowledge should require changing only one fragment of code.

This ensures consistency, reduces the risk of bugs, and simplifies testing. DRY makes code stable and transparent when it comes to recurring data structures, formulas, business rules, calculations, or constants.

Where DRY Really Works

Everything related to backend logic is where DRY shines. For example:

  • shared database access functions
  • centralized error handling
  • unified pricing and discount calculation rules
  • recurring data models and validation

In these areas, duplicating logic leads to divergence and unpredictable behavior. Here, DRY doesn’t just save lines of code — it preserves the integrity of the system’s meaning.

When DRY Turns Into a Trap

The Risk of Over-Abstraction and Poor Readability

The biggest mistake is applying DRY automatically without context.

The desire to “combine everything” often creates monstrous abstractions.

Developers merge similar chunks of code and end up with a giant function full of conditions, flags, and optional parameters that tries to do everything — but no one remembers exactly what.

Such “universal” modules become black boxes: hard to modify, painful to debug, and confusing to newcomers.

Once, I worked on a project where I just needed to change the color of a button on one page. But the button component was so “dried up” — with factories, dozens of props, and global styles — that it took me four hours to make a tiny change. Ironically, there were only about ten buttons in the entire interface. That was a textbook example of how blind DRY can turn simplicity into chaos.

Tight Coupling and Fragile Design

When everything depends on one shared module, every small change becomes a minefield. You fix something for one page — and something breaks elsewhere. This creates tight coupling, where independent parts of the system become hostages of shared logic.

Ironically, the famous DRY argument “fix it once, fix it everywhere” works in reverse: introduce a bug once — and it spreads everywhere. The result is a fragile, rigid system where touching one line can cause a chain reaction.

Premature Abstraction – the Enemy of Flexibility

DRY was never meant to eliminate every visual repetition — only repetition of knowledge. But developers often merge pieces that are only visually similar, not conceptually identical. At first, it feels smart: “Why write this twice?”. Then you realize these pieces evolve differently, and the abstraction becomes a burden.

In startups, I see this all the time. Requirements change weekly, designs daily. Premature abstraction slows everything down — you keep adding exceptions, flags, and branching logic just to preserve a “shared” structure.

It’s often far easier to duplicate a small piece of code, let it evolve independently, and only later refactor it once the functionality stabilizes. This follows the “Rule of Three”: if you write the same thing three times, that’s the moment to abstract.

Architectural Traps and the “Distributed Monolith”

In microservice architectures, DRY can be downright dangerous.

Developers create shared libraries “to avoid duplication,” but in reality, they end up with a distributed monolith — a system where all services depend on one shared core.

Now you can’t deploy anything independently; every minor update risks breaking multiple components. Over time, those shared “commons” libraries turn into dumping grounds for everything no one dares to delete. Instead of elegance, DRY breeds technical debt.

Frontend teams are especially vulnerable: shared “UI utility” libraries quickly become bloated, outdated, and terrifying to touch. Everything looks dry, but nobody understands how it works anymore.

When DRY Conflicts With Common Sense

In the real world, systems are rarely symmetrical or stable.

In frontend projects especially, “universal components” often become nightmares: a single mega-component for buttons, taking ten props and supporting countless variants — until someone clones it anyway to make “a rounded one with a gradient hover.”

When the designer suddenly wants three shapes of buttons, forty color shades, and unique hover states, the DRY dream collapses. Sometimes it’s far better to have three separate, simple components than one monstrous one.

In young, evolving projects, universal abstractions don’t speed things up — they create inertia. When the priority is speed, iteration, and experimentation, duplication is often cheaper and safer.

When It’s Better to “Wet” Your Code

Sometimes, duplication is an intentional, pragmatic decision.

If similar pieces of code serve different purposes, evolve at different paces, or belong to different contexts — keep them separate. This mindset even has a name: WET — Write Everything Twice.

It’s not an argument for chaos — it’s an acknowledgment that duplication is often cheaper than the wrong abstraction. This is especially true in frontend development and young products, where requirements change faster than architecture can stabilize.

In business logic and backend development, DRY still shines — for calculations, validation, and data access layers. But in user interfaces and early-stage products, pragmatism must win over dogma.

Conclusion

DRY is an excellent principle — when used consciously. It keeps systems consistent and maintainable. But when treated like a religion, it becomes a source of rigidity and pain.

In frontend development and fast-moving projects, dogmatic DRY causes more harm than good. The faster your product evolves, the more important flexibility, independence, and clarity become.

Let the code repeat a little — as long as it stays readable, local, and easy to change. After all, programming isn’t about eliminating repetition — it’s about balancing clarity, speed, and common sense.

Top comments (0)