Protected Variations tells you to wrap predicted points of instability. How do you decide *which* points genuinely deserve protection, and when does applying it make the design worse?
answer
- variation point (now) vs evolution point (maybe)
- protection = option purchase, price it
- retrofit cost decides: cheap → wait
- rule of three; wrong abstraction > duplication in cost
- name the change or don't build the seam
basics
~20 sProtect where change is genuinely likely and expensive to retrofit — external systems, business rules, regulated logic, vendor choices. Skip it where change is unlikely or cheap to make later; unused abstractions are pure cost: more indirection, harder reading, harder debugging.
solid answer
~50 sProtection is an option you buy, so price it. Weigh **probability of the change**, **cost of the ripple if unprotected**, **cost of retrofitting later**, and **cost of the abstraction now**. Larman's split helps: **variation points** — several behaviours required *today* — nearly always justify protection, because you already know the axis of change. **Evolution points** — speculative future variation — need evidence: a roadmap item, a regulatory deadline, a vendor contract expiring, a second customer, a known migration. Without evidence you get **speculative generality**: an abstraction shaped around a guess, which typically fits the *wrong* axis when real change arrives, and must be dismantled first. The **rule of three** is the practical guard — write it concretely, duplicate it deliberately the second time, abstract on the third, when the real axis is visible. Also weigh reversibility: a decision that is cheap to reverse later doesn't need a protective layer now.
go deeper
Say that you protect things likely to change (external APIs, business rules, vendors) and don't add abstraction 'just in case'; mention that extra layers make code harder to read.
Distinguish variation points from evolution points, invoke YAGNI and the rule of three, and name concrete signals for likely change.
Frame it as an option purchase over probability, ripple cost, retrofit cost and present cost; use reversibility as the tiebreaker and explain why a wrong abstraction can beat duplication only in one direction.
Add organisational scope: protect what crosses ownership boundaries and published contracts, invest in making change cheap so less prediction is needed, and treat 'stable interfaces' as long-term commitments with a deprecation policy.
## Terms - **Protected Variations (PV):** identify points of *predicted* variation or instability and place a stable interface around them. - **Variation point:** variation the system must support **now** (three tax jurisdictions live in production). - **Evolution point:** variation **anticipated** but not currently required ("we might add a second payment provider one day"). - **Speculative generality:** Martin Fowler's code smell — machinery (abstract types, hooks, parameters, plugin points) built for a need that never materialised. - **YAGNI** ("You Aren't Gonna Need It"): don't build for imagined requirements; add capability when demanded. - **Rule of three:** first occurrence concrete; second duplicated on purpose; third earns the abstraction — by which point the *shape* of the variation is observed rather than guessed. ## The decision, made explicit A protective seam is an **option purchase**. Four quantities: 1. **P(change)** — how likely is this to vary? 2. **Cost_ripple** — if it changes and there is no protection, how many places must be touched, and how risky is that edit? 3. **Cost_retrofit** — how expensive is it to *introduce* the seam later, once the change is real? 4. **Cost_now** — indirection, extra types, worse navigability, more tests, slower onboarding, and the risk of guessing the axis wrong. Protect when `P(change) × (Cost_ripple + Cost_retrofit) > Cost_now`. Two corollaries interviewers like: - **Low retrofit cost is a licence to wait.** Extracting an interface inside one well-tested module is a mechanical refactor a tool can perform. That change is *reversible*, so protecting it up front is a bad trade. - **High retrofit cost forces early protection.** Anything crossing a boundary you do not control — a published API, a persisted data format, a wire contract between independently deployed services, a database schema with live consumers, a vendor SDK woven through hundreds of call sites — is expensive or impossible to retrofit. Protect those on day one even without a concrete second implementation. ## Where the evidence for "predicted" comes from Not from imagination. Legitimate signals: - **History:** this file changed 40 times in a year (change frequency from version control is the single best predictor). - **Contracts and roadmaps:** a vendor contract ends in 18 months; the roadmap names a second market. - **Regulation:** tax, privacy, retention rules change by statute, on schedules you don't control. - **Externality:** anything owned by someone else varies on their timetable, not yours. - **Multiplicity today:** you already have two of something. Illegitimate signals: "we might", "it's more flexible", "a real architect would", "in case we go multi-tenant someday". ## How PV makes designs worse 1. **Wrong axis.** You parameterised the algorithm; the real change was the data format. The abstraction now *obstructs* the change and must be removed first — worse than having had no abstraction. 2. **Lowest-common-denominator contracts.** Two providers forced behind one interface: capabilities are stripped, or capability flags leak the difference back to callers, so the client branches anyway. 3. **Cognitive cost.** Every seam turns "read the code" into "find the implementation". At scale this dominates: many teams' slowest activity is comprehension, not typing. 4. **False safety.** People believe a layer exists, so they don't check whether provider semantics (timeouts, ordering, error taxonomy, transactional scope) leak — and they always leak somewhere. 5. **Protection with a single implementation forever.** One interface, one implementor, no third party, no test double needed → pure overhead. (Testing *is* sometimes a legitimate second implementation — but only if you actually need a seam to test, and not when the real collaborator is fast and deterministic.) 6. **Abstraction ossifies the wrong model.** Once several teams depend on your "stable" interface, changing *it* becomes the expensive act — you have merely relocated the rigidity. ## The complementary discipline PV is about *anticipating*. The counterweight is *keeping change cheap*: small modules, high cohesion, few dependents, strong tests, fast pipelines, reversible decisions. A codebase where extracting a seam takes an hour can afford to guess less. Framed that way, **PV and YAGNI are not in conflict**: YAGNI governs evolution points with no evidence; PV governs variation points and high-retrofit-cost boundaries. The disagreement is only ever about how strong the evidence is. ## A workable checklist Before adding a protective abstraction, answer: - What *specific* change am I protecting against? (If you can't name it, stop.) - What is the evidence it will happen, and roughly when? - What breaks today if I skip this and it happens in a year? - Can this seam be introduced later by a mechanical refactor? (If yes → wait.) - Does the boundary cross something I don't control? (If yes → protect now.) - Is there a second real implementation, or only a hypothetical one?
- How do you reconcile Protected Variations with YAGNI?They divide by evidence. YAGNI rules speculative evolution points — no named change, no protection. PV rules real variation points (several behaviours needed now) and boundaries where retrofitting is expensive or impossible, such as published APIs, persisted formats and contracts between independently deployed services.
- What signal do you use to find genuine points of instability in an existing codebase?Change frequency from version control — files and modules with the highest churn, especially those whose churn drags many other files with them in the same commits. Combine that with externally owned dependencies and regulation-driven logic; those change on someone else's schedule.
- Why can a wrong abstraction be worse than duplication?Duplication is visible and locally fixable; a wrong abstraction is a shared, load-bearing constraint. When real change arrives along a different axis, every consumer must be understood and unwound before the change can even begin — so the abstraction adds a demolition step to the work.
Insurance. You insure the house (unlikely event, catastrophic and irreversible loss) and not the toaster (cheap to replace when it fails). Insuring everything isn't prudence — the premiums are paid every single day, by every person who has to read the code.
saying these in an interview costs you the question
- "Always wrap external libraries, no exceptions" — stated without weighing surface area, retrofit cost, or how many call sites exist.
- Treating Protected Variations as a licence to add abstraction anywhere, and dismissing YAGNI as junior advice.
- Justifying a seam with "it's more flexible" or "we might need it later" instead of a named, evidenced change.
- Claiming duplication is always worse than a wrong abstraction — a premature abstraction must be dismantled before the real change can be made.
- Ignoring reversibility: cheap-to-retrofit seams should usually be deferred, expensive/irreversible boundaries protected immediately.
- Adding an interface solely so a mocking framework can be used, when the real collaborator is fast, deterministic and side-effect-free.