When to Extract a Shared Component
Extraction is a bet that a shape is stable — not a rule about counting to three. The third copy just happens to be where you finally have enough evidence to judge.
Recently I went through an interface and counted how many times the same little tag appeared — the small monospace badge that shows a category. It was in seven places. Not one component used seven times: seven separate copies of the same idea, each with slightly different padding, each written at a different moment, all pretending to be the same thing.
My first instinct was the honest one: pull it into a single component and delete the copies. But I've learned to distrust that instinct when it fires on reflex, because the reflex that rescues you from drift can just as easily lock you into the wrong abstraction — one that fit the first few cases and quietly fights every case after.
So I don't really think of extraction as a rule about counting. I think of it as a bet. Pulling something into a shared component is a bet that its shape is stable: that the thing repeating is the same thing, and will keep being the same thing as the code around it changes.
Extraction is a bet that a shape is stable — and the only real question is how much evidence you have before you place it.
Same, or just rhyming?
This is where the old "rule of three" earns its keep, as long as you don't treat it as a law. The first time you write something, you just write it. The second time, you notice, and you file it away. By the third, you usually have enough examples to see which parts are genuinely the same and which only looked alike — and that, not the number itself, is the point of waiting. Sometimes two copies are already, obviously, one primitive, and you extract at two. Sometimes you have ten near-identical things that should stay ten, because they're changing for different reasons.
What varies across the copies is what tells you which case you're in. If two badges differ only in padding, that's a prop. If a third one also changes its behaviour, its layout, and what it links to, that's a hint it was never the same component — it just rhymed. The differences are the evidence; the count only buys you enough of them to read.
When the abstraction is the debt
Extracting too early has a cost that's easy to underrate, because it doesn't look like a cost — it looks like clean code. The common version is the primitive that slowly becomes a machine for every case it was ever asked about. It starts as one clean button. A fourth case needs something slightly different, so it grows a prop; a fifth, another; and before long the component takes a dozen flags and reading its call sites tells you nothing, because it does everything and so means nothing.
A quieter version is logic baked into a component that should only render. A card that fetches its own data and decides what to show is easy to write once and painful to reuse, because the next screen wants the same shell with different logic. The fix is a deliberate boundary between rendering and decisions: the component shows, something else — often a hook — decides what. Keep the domain decisions out and the shape stays reusable.
And sometimes two things look identical today but are changing for different reasons — a form in one flow, a form in another that only resembles it. Merge them and every future change to one drags the other along. This is the case people miss, because on the screen the two look like obvious duplication. A little repeated markup is cheap to fix later. An abstraction that ties two unrelated features together is not.
What actually decides it
So the honest answer to when is less about a number than about the bet. Extract once you've seen the shape enough times to believe it's stable. Keep the surface small and additive, so a new case adds an option instead of reshaping the whole thing. Push the logic outward, so the reusable part stays a shape and not a decision.
Do that and the goal takes care of itself: you're not removing duplication for its own sake, you're extracting to make the next change cheap. Reach for it too early and you've traded seven honest copies for one dependency that fits none of them — and that one is harder to fix, because now it's hiding somewhere you have to go looking for it.