Duplication Is the Cost of Freedom
Don’t Repeat Yourself. It is the first commandment whispered to every junior developer. It is treated as an objective moral good. The industry has elevated D.R.Y. from a helpful heuristic to a religious fetish. This dogma is killing your system. Every time you collapse two similar blocks of code into a single shared abstraction, you are not saving time. You are building a suicide pact. You are weaving threads of dependency that will eventually choke the life out of your engineering velocity.
Software engineering is the management of complexity. Complexity is not a fixed quantity. It is a gas that expands to fill the volume of the container you provide. When you abstract code to avoid duplication, you are trying to compress that gas. You think you are making the system simpler by reducing the line count. This is a delusion. You are merely moving the complexity into the connections between components. You are trading a small amount of repetition for a massive amount of coupling. Coupling is the true killer of large-scale systems. It creates a reality where a change in a billing module accidentally breaks a notification service because they shared a 'helper function' that grew too many responsibilities.
Standardization is often a trap. In a manufacturing plant, a mold is used to create a thousand identical parts. If the mold breaks, every part is flawed. If you need to change one part, you must change the mold, which changes all thousand parts. In software, we pretend that this is efficiency. We call it a single source of truth. In reality, it is a single point of failure. Modern software architecture should not resemble a single, intricate clockwork mechanism. It should resemble a collection of independent, isolated cells. Each cell must be able to mutate, fail, or be replaced without the others noticing. Duplication is the membrane that allows this isolation to exist.
Consider the lifecycle of a feature. Two different departments need to calculate a tax rate. Today, the logic is identical. The clever engineer sees this and creates a SharedTaxCalculator. They feel a surge of dopamine. They have saved fifty lines of code. Six months pass. The legislative landscape changes. Department A needs to account for a new municipal surcharge. Department B does not. The engineer is now forced to add a boolean flag to the SharedTaxCalculator. A year later, another edge case appears. More flags. More conditional branches. The shared abstraction is no longer a clean mathematical function. It is a scarred, bloated monster. It is a janitor’s closet filled with specialized tools that nobody understands. The cost of maintaining this abstraction now far exceeds the cost of having two identical functions.
This is the convergence trap. Two things that look the same today are almost never the same thing in the long run. They are merely in a temporary state of visual similarity. By forcing them into a shared structure, you are gambling that their future requirements will evolve in lockstep. This is a losing bet. Business requirements are chaotic. They do not care about your clean code principles. When you couple unrelated domains through shared code, you are shackling your future self to the decisions of your past self. You lose the ability to move fast because every change requires a full-system audit to ensure no downstream side effects occur.
Copy-paste is the only true decoupling. It is the architectural equivalent of a clean break. When you copy a block of logic from Service A to Service B, you are declaring that these two services are sovereign entities. They are allowed to diverge. If Service A needs to change, Service B remains untouched. There is no shared library to update. There is no version conflict. There is no fear. The cost is a few extra bytes of disk space and a few extra seconds of typing. In an era of infinite storage and high-speed networks, optimizing for line count is an archaic obsession. We should be optimizing for changeability. We should be optimizing for the ability to delete entire sub-systems without the whole house of cards falling down.
Abstraction is a one-way street. It is easy to combine two things. It is excruciatingly difficult to untangle them once they have been fused. Engineers often suffer from a pathological fear of redundancy. They see two identical classes and feel a physical itch to merge them. They should resist this impulse. Redundancy is a safety mechanism. In aerospace engineering, redundant systems are mandatory. If one flight computer fails, the second one takes over. They are not 'abstracted' into a single computer to save weight. They are kept separate to ensure survival. Software should be approached with the same mindset. Isolation is the foundation of resilience.
Maintaining this isolation does not mean we must suffer from manual toil. The goal is to avoid architectural coupling, not to maximize the number of keystrokes. Tools like TextExpander allow engineers to maintain the speed of repetition without the risk of creating a centralized dependency. By using snippets and templates, you can stamp out proven patterns across different services while keeping the actual implementation code decoupled. You get the benefits of consistency without the structural rot of shared libraries. It is a way to scale knowledge without scaling technical debt. You are automating the repetition, not abstracting the logic.
Shared libraries are a tax on every developer in the organization. Every time a core library is updated, every team must stop what they are doing to integrate the change. They must run their tests. They must deal with breaking changes. This is a massive hidden cost that never shows up on a Jira board. It is a slow, grinding friction that reduces an organization’s velocity to a crawl. If each team simply owned their own copy of the logic, they could update on their own schedule. They could ignore changes that don't apply to them. They could move at the speed of their own business domain rather than the speed of the slowest common denominator.
We must stop treating 'boilerplate' as an insult. Boilerplate is often just the necessary overhead of isolation. It is the unyielding concrete wall between two rooms. You could replace the wall with a thin curtain to save space, but then you lose the privacy and the soundproofing. In software, the 'sound' is the side effects of code changes. A system with zero duplication is a system where every part is vibrating in resonance with every other part. One wrong note and the whole structure shatters. A system with strategic duplication is dampened. It is quiet. It is stable.
Senior engineers should be judged by how much code they allow to exist in isolation, not by how many 'elegant' frameworks they build. Elegance is a trap. It is a vanity metric. A system is elegant if it can be understood by a tired engineer at three in the morning. A system is elegant if a new hire can change a feature in ten minutes without fearing a global outage. Shared abstractions are almost never easy to understand. They require the reader to hold the entire hierarchy of dependencies in their head. They require an understanding of how the abstraction handles ten different use cases, nine of which are irrelevant to the task at hand.
Duplication is honest. It shows exactly what the code is doing without hiding it behind layers of indirection. It is easy to debug. It is easy to profile. It is easy to delete. When you are done with a feature, you can simply remove the code. If that code is buried inside a shared utility used by fifty other features, you can never delete it. It becomes a permanent part of the system’s geology. You are forced to carry it forever. This is how legacy systems become unmanageable. They are not burdened by too much code; they are burdened by too many connections.
Precision requires the rejection of easy answers. D.R.Y. is an easy answer. It is a shortcut that feels like a long-term win. It is the architectural equivalent of taking out a high-interest loan. You get the cash today—shorter files, fewer lines—but the interest payments will eventually exceed the principal. The interest is the time spent debugging shared side effects. The interest is the meetings spent negotiating changes to a shared API. The interest is the inability to innovate because the core of the system is too brittle to touch.
Accept the duplication. Embrace the copy-paste. Build stamps, not molds. Let your services be lonely. Isolation is the only path to a system that can survive the collision with reality. If you find yourself writing the same logic for the third time, don't reach for an interface. Reach for a snippet. Keep the code separate. Keep the teams independent. Keep the system alive. The cost of repetition is measured in milliseconds of typing. The cost of premature abstraction is measured in months of wasted engineering effort. Choose wisely.
Not sure which tools to pick?
Answer 7 questions and get a personalized stack recommendation with cost analysis — free.
Try Stack AdvisorEnjoyed this?
One email per week with fresh thinking on tools, systems, and engineering decisions. No spam.

