
Prefer duplication over the wrong abstraction: Sandi Metz's warning, a decade on
In a 2014 RailsConf talk and a 2016 essay, Sandi Metz argued that duplication is far cheaper than the wrong abstraction, described the eight-step path by which a shared helper becomes a condition-laden trap, and explained the way back.
Duplication is far cheaper than the wrong abstraction
At RailsConf 2014, in a short section of her talk "All the Little Things", Sandi Metz made a claim that still circulates in software teams: duplication is far cheaper than the wrong abstraction. Her advice was blunt — prefer duplication over the wrong abstraction. The reaction was strong enough that she later expanded the idea into an essay, first written for her Chainline newsletter and re-published on her blog in January 2016. Some developers told her she had lost her mind; far more said the pattern was everywhere in their own codebases.
How a good abstraction becomes a trap
Metz described a repeating story. Programmer A notices duplication, extracts it, gives it a name — a method, maybe a class — and replaces every copy with the new abstraction. The code looks perfect. Time passes. A new requirement arrives that the abstraction fits almost, but not exactly. Programmer B feels obliged to keep it, so they add a parameter and a conditional. Another requirement brings another parameter and another conditional, and the loop runs until the code is incomprehensible.
The trap is psychological as much as technical. Existing code argues for its own survival: its presence suggests it is correct and necessary, and it represents effort that someone wants to protect. The messier the code, the stronger the pressure to keep it — the sunk cost fallacy. By the time the next developer arrives, the shared helper is no longer one idea but a condition-laden procedure that is hard to understand and easy to break.
The way back: inline, prune, re-extract
Her prescription is counterintuitive: when the abstraction is wrong, the fastest way forward is back. Inline the abstracted code into every caller, use the parameters inside each caller to work out which part it actually needs, and delete the rest. That removes the abstraction and the conditionals together, leaving each caller with only the code it uses.
Teams that tried it often found the callers were never really doing the same thing. With the old abstraction gone they could re-isolate duplication and re-extract from what they had learned, and feature work got faster. Metz's rule of thumb: if you are passing parameters and adding conditional paths through shared code, the abstraction is incorrect. Abandoning it early hurts less than defending it. "When the abstraction is wrong, the fastest way forward is back," she wrote. "This is not retreat, it's advance in a better direction."
SiTech — AI-powered web development
We build fast, modern websites and bring AI into real business workflows. Have a project or a question? We'd love to help.