Draft / Scheduled Content
This article is a draft or scheduled for future publication. The content is subject to change.
The Fallacy of DRY: Why You Should Write Duplicated Code First
The DRY Religion
Of all the programming acronyms in the software industry, none is as widely worshipped as DRY: Don’t Repeat Yourself.
We teach it to juniors on day one. We enforce it aggressively in code reviews. If a developer notices the same three lines of code written in two different files, they immediately feel a physical itch to refactor it, extract it into a shared helper function, and reuse it.
We do this because duplication is supposedly the root of all evil. If you duplicate code, you have to maintain it in two places, and if you fix a bug in one, you’ll forget to fix it in the other.
This advice sounds logical.
But in practice, the dogmatic pursuit of DRY has caused far more architectural damage, code complexity, and maintenance headaches than duplication ever has.
Developers are so afraid of duplication that they build bad abstractions to avoid it. They fail to realize a fundamental truth of software engineering: duplication is far cheaper than the wrong abstraction.
The Birth of the Wrong Abstraction
Let’s trace how the fear of duplication ruins a codebase.
You are building a web app. You have a UserCard component on the admin dashboard and a UserCard component on the public profile page. They look almost identical: they show the user’s name, avatar, and join date.
Because you are a DRY-compliant developer, you say: “I will not duplicate this HTML and styling! I will create a single, reusable UserCard component and call it in both places.”
It works beautifully. You saved 20 lines of code.
A week later, the product manager requests a change: “On the admin dashboard, we need to show the user’s email and a ‘Deactivate’ button on the card.”
You don’t want to duplicate the card, so you add props to your abstraction:
// The abstraction begins to bloat
interface UserCardProps {
user: User;
showEmail?: boolean;
isAdmin?: boolean;
onDeactivate?: () => void;
}
Another week passes. The PM returns: “On the public profile, if the user is a premium member, we should show a gold badge and hide their join date.”
You add more props:
interface UserCardProps {
user: User;
showEmail?: boolean;
isAdmin?: boolean;
onDeactivate?: () => void;
showPremiumBadge?: boolean;
hideJoinDate?: boolean;
}
Now look inside the component code. It is littered with conditional logic:
const UserCard = ({ user, showEmail, isAdmin, showPremiumBadge, hideJoinDate }: UserCardProps) => (
<div className={`card ${showPremiumBadge ? 'gold-border' : ''}`}>
<img src={user.avatar} />
<h2>{user.name}</h2>
{!hideJoinDate && <p>Joined: {user.joinDate}</p>}
{showEmail && <p>{user.email}</p>}
{isAdmin && <button onClick={...}>Deactivate</button>}
</div>
);
This component is no longer a single, clean abstraction. It is a condition-laden machine trying to serve two completely different masters. The code is hard to read, hard to test, and highly fragile.
If a developer changes the styling of the card for the public profile, they risk accidentally breaking the layout of the admin dashboard. The two domains are now tightly coupled.
+-----------------------------------------+
| Shared "UserCard" Component |
| - Rules for Public Profile Page |
| - Rules for Admin Dashboard Page |
+-----------------------------------------+
| |
(Tight Coupling)| | (Brittle!)
v v
Public Page Admin Page
The Solution: Write Everything Twice (WET)
If you had simply duplicated the UserCard component at the beginning—writing a PublicUserCard and an AdminUserCard—what would have happened?
Yes, you would have had 40 lines of HTML instead of 20.
But when the requirements diverged, you could have updated the AdminUserCard in isolation without touching the PublicUserCard. No props would have been added. No conditional statements would have been written. The code would have remained simple, focused, and decoupled.
This is the WET principle: Write Everything Twice (or Avoid Hasty Abstractions - AHA).
Duplication is not an error; it is an opportunity to gather information. When you see duplication for the first or second time, you do not have enough context to know if the similarity is permanent or coincidental.
Are these two blocks of code identical because they represent the same business concept, or are they identical by pure coincidence?
If they are identical by coincidence, they will diverge the moment requirements change. If you abstract them too early, you freeze that coincidence into an architectural foundation, forcing future developers to write complex configurations to bypass your abstraction.
When to Abstract
You should only extract code into an abstraction when:
- The duplication represents a core, immutable rule: If you are calculating tax rates or validating password complexity, that logic must be identical across the app. If it drifts, it is a business bug. This is true duplication that must be DRY.
- You have seen the duplication three or more times (The Rule of Three): By the third time you write the same logic in different contexts, the real underlying pattern becomes clear. You know what parameters change, what stays constant, and what the clean interface should look like.
- The abstraction reduces cognitive load: If extracting a function makes the caller code significantly easier to read and reason about, do it. If the abstraction requires a page of documentation to explain how to use its configuration props, keep it inline.
Accept the Mess
Many developers write abstractions because they have an aesthetic preference for short files and zero duplication. They want their codebase to look like a clean, mathematically perfect proof.
But software engineering is a practical discipline, not an art gallery.
A little duplication is fine. It keeps modules decoupled, makes code easy to delete, and allows features to evolve independently.
Stop abstracting on the first occurrence. Accept the duplicate code. Wait for the requirements to show you the way.
Related Content
Clean Code: The Cult of Dogma and Why Your Abstractions Are Probably Wrong
Robert C. Martin's Clean Code shaped a generation of developers, but its dogmatic rules about tiny functions, obsessive DRY, and terrible example code have caused more harm than good. Here's what the book got right, what it got catastrophically wrong, and what to read instead.
Your Unit Tests Are Mocking You
Unit testing with mocked dependencies has become a software industry obsession. We write tests that verify our code behaves against mock assumptions, resulting in green test suites that pass while production crashes. It is time to embrace integration tests with real, lightweight dependencies.
O (Open/Closed) from SOLID
Master the Open/Closed Principle from SOLID. Learn how to design software that is open for extension but closed for modification, so new features don't break existing code.