practice

Rule of Three

also called Abstract at the Third Instance, Duplicate Before Abstracting

Hard-code the first case, copy for the second, abstract at the third - because only the third instance reveals which parts actually vary.

evolutionary-architectureurban-companypremature-abstractionseamsvariation

A generic mechanism built before the second instance encodes the first instance's assumptions in a form that is harder to change than a hard-coded version. This is the counter-intuitive part: premature abstraction is worse than duplication, because duplication is visible and an abstraction with the wrong shape is not.

Why it matters

Fast-growing products face a constant pull toward generality — the second city, the second category, the second tenant type is always visible on the roadmap. Building for it feels responsible. But a plugin system, a rules engine or a workflow abstraction built from imagination is generic in the wrong dimensions, and it is then defended because it was expensive.

Implementation patterns

  • Hard-code the first, without apology, and keep it readable.
  • Copy for the second, deliberately, and note the diffs. The diff list is the specification for the eventual abstraction and it is free.
  • Abstract at the third, when the axis of variation is observed rather than predicted.
  • Except where variation is certain. If you know pricing rules will differ by city and compliance rules by jurisdiction, an interface there costs almost nothing on day one and is the difference between a configuration change and a rewrite later. Certainty, not likelihood, is the bar.
  • Sort decisions by cost of reversal. Data model, partition key, tenancy key, identity scheme and public API contracts are expensive to change and deserve early effort. Service splits, caching, queue choice and internal structure are cheap and can be discovered.

Industry example

A services marketplace such as Urban Company launches with one city and one category and reaches dozens of both. The decisions that had to be right on day one were not the flexible ones: city and category as first-class identifiers in the first row of data, because retrofitting a partition key into a live schema is among the worst migrations there is, and adding a column nobody reads is free.

The decisions that should have waited were the rules engine and the plugin system — because until the fourth category, nobody knew whether variation lived in pricing, in scheduling, in qualification requirements or in compliance, and it turned out to be all four in different proportions.

Failure scenarios

  • The framework built for two, which fits the second case perfectly and the third not at all.
  • Configuration that is really code, where the "flexible" mechanism ends up requiring a deploy anyway, plus an interpreter nobody can debug.
  • Abstraction defended by sunk cost, so the third instance is contorted to fit rather than the abstraction reshaped.
  • The opposite failure: deferring the schema decisions too, and discovering in year two that there is no tenant key and no way to add one online.

Trade-offs

Waiting for the third instance means the second is built with duplication, and duplication has a real cost — divergent bug fixes, inconsistent behaviour, twice the tests. In a small team the copy is cheap; in a large one the two copies may end up owned by different teams and diverge permanently.

The mitigation is to make the duplication deliberate and documented — a comment naming the other copy, and a note on the backlog — so the third instance triggers the abstraction rather than becoming a third copy.

Interview question

"Your product will launch in a second country next quarter with different tax and compliance rules. What do you abstract now, what do you copy, and what do you hard-code — and how would you defend the copy to a reviewer who calls it technical debt?"