How do you build a system that stays cheap to change without violating YAGNI by building speculative extension points? Discuss the mechanisms you would actually use.
answer
- Buy options, not features
- Last responsible moment; one-way vs two-way doors
- Expand/contract makes irreversible reversible
- Tolerant reader + additive-only + versioned envelope
- Fitness functions = tests for architecture
basics
~20 sBuy options, not features: keep code well-factored and tested, defer decisions to the last responsible moment, make data and contracts evolvable (versioned, additive-only, expand/contract migrations), and spend up-front design only on decisions that are hard to reverse.
solid answer
~50 sThe resolution is that changeability comes from *cheap change*, not from pre-built flexibility. Mechanisms: (1) high cohesion and low coupling so change stays local — modules organised around reasons to change, not layers; (2) a fast, trustworthy test suite and continuous refactoring, which is what makes "add it later" actually cheap and is the precondition Fowler attaches to YAGNI; (3) evolvable contracts — versioned, additive-only schemas and tolerant readers, so external interfaces can grow without coordinated releases; (4) expand/contract (parallel change) migrations, so schema and API change is routine rather than exceptional; (5) explicit classification of decisions: two-way doors default to defer, one-way doors (published contracts, persisted formats, security and isolation boundaries, structural NFRs) get deliberate design and an ADR; (6) evolutionary architecture with fitness functions — automated checks (dependency rules, latency budgets, coupling limits) that let the architecture change safely. Note the difference: none of these builds an unused feature; each lowers the cost of the change when it arrives.
go deeper
Say that keeping code clean and well-tested is what makes adding things later cheap, and that you should avoid building unused hooks. One or two concrete mechanisms is enough.
Name specific mechanisms — cohesive modules, seams at real external boundaries, versioned and additive-only schemas — and explain that these lower the cost of change rather than pre-building the change.
Frame it as buying options versus positions; cover expand/contract migrations, tolerant readers, decision classification by reversibility, and the last responsible moment. Note Fowler's precondition that YAGNI requires tests and refactoring.
Add the organisational layer: fitness functions and ADRs as continuous architectural governance, Conway's-law effects on what "cheap change" means across teams, deliberately converting one-way doors into two-way doors by investing in migration machinery, and how you set and police these defaults across many teams.
## The apparent paradox YAGNI says don't build for imagined futures. Architecture is supposedly about decisions that are hard to change later. Reconciling them is the senior/principal question. The resolution: **do not pre-build flexibility — reduce the cost of change instead.** In options language, you buy *options* (cheap, reversible, no unused code) rather than *positions* (a built feature nobody uses). ## Mechanism 1 — structure that localises change - **Cohesion by reason to change.** Group code that changes together for the same reason (a form of the Single Responsibility Principle / "axis of change"). If a typical requirement touches one module, change is cheap without any extension point. - **Low coupling at real boundaries.** Put seams where the world already imposes one — network, storage, third-party service, another team — not at invented internal boundaries. Boundaries at genuine external edges pay for themselves; invented ones are speculative generality. - **Boring, standard technology.** Every bespoke framework is a carry cost forever. Fewer, more familiar moving parts = cheaper change. ## Mechanism 2 — the safety net that makes deferral rational Fowler is explicit that YAGNI presumes **clean code, good tests and continuous refactoring**. Without them, "we'll add it later" is a lie, because later is not cheap. So the investments that *look* like extra work — fast test suites, a deployment pipeline, observability — are precisely what license YAGNI everywhere else. This is the argument to make when someone claims YAGNI equals sloppiness. ## Mechanism 3 — evolvable contracts instead of speculative features For interfaces you cannot change unilaterally: - **Additive-only evolution**: never repurpose or remove fields; add optional ones. - **Tolerant reader / must-ignore-unknown-fields** (Postel-style discipline on consumers) so producers can add fields without breaking anyone. - **Explicit versioning** in the envelope from v1, plus a stated deprecation policy. - **Consumer-driven contract tests** to know who actually depends on what. - **Keep the surface small and private for as long as possible** — the cheapest way to stay flexible is to have fewer committed consumers. Each costs near-zero and is not a presumptive feature; it preserves the option to change. ## Mechanism 4 — make irreversible things reversible - **Expand/contract (parallel change)** for schema and API change: add the new shape, dual-write/dual-read, backfill, migrate readers, then remove the old. Once this is routine, "we must get the schema right first time" stops being true, which converts one-way doors into two-way doors. - **Strangler fig** for replacing subsystems incrementally rather than by rewrite. - **Feature flags with a removal policy** — legitimate for progressive delivery of code that exists; illegitimate as permanent speculative switches. Every flag needs an owner and an expiry, or it becomes carry cost. - **Capture data early even when you don't process it.** Data you never recorded is unrecoverable; this is one of the few genuine "do it now" cases, and it is cheap. ## Mechanism 5 — classify the decision, then choose the default - **Two-way door** (Amazon's term) / reversible: default to YAGNI, decide fast, defer to the **last responsible moment** (Lean/Poppendieck: the moment after which the cost of delay exceeds the cost of deciding, i.e., when delaying would eliminate an important alternative). - **One-way door**: published contracts, persisted formats, security/tenancy isolation, choice of consistency model, coordination-heavy cross-team commitments, structural NFRs (latency, availability, residency, audit). Spend design effort, write an ADR recording the decision, its context and the options rejected — the record itself is cheap optionality because it tells future readers what to revisit. ## Mechanism 6 — evolutionary architecture with fitness functions From Ford, Parsons and Kua: define **fitness functions** — automated, continuously-run checks encoding the architectural characteristics you care about (module dependency rules, cyclic-dependency bans, p99 latency budgets, bundle size, error budgets, coupling metrics). They give architecture the same safety net tests give code: you can change the structure confidently because violations surface immediately. This is the principal-level answer to "how do you keep architecture changeable without designing it all up front". ## What this rules out - speculative microservice decomposition "for future scale" (a coordination cost, not a feature) - an internal framework or abstraction layer over a vendor you have no plan or budget to replace - plugin systems and configuration surfaces with a single real configuration - multi-tenancy, i18n or workflow engines built before the first paying case, *unless* they sit in the one-way-door category (isolation/security often does — decide explicitly) ## Counterweights and failure modes - **Chesterton's fence**: don't remove flexibility you don't understand; find the requirement that produced it. - **Gall's law**: complex systems that work evolve from simple systems that worked; a complex system designed from scratch rarely works. Supports starting simple. - **Under-design is real too.** Fowler's "Is Design Dead?" argues evolutionary design *without* refactoring skill and tests degrades to a mess. The absence of up-front design is only safe when the change machinery is genuinely in place. - **Organisational reality (Conway's law)**: some "flexibility" is really about who can change what without a meeting. Reducing cross-team coordination is often the highest-leverage way to make change cheap.
- When is building a genuinely generic mechanism up front justified?When variation is a stated present requirement rather than a guess — for example the product's core value is user-defined rules or third-party plugins — or when the mechanism sits behind a one-way door such as a published contract or a tenant-isolation boundary. In both cases evidence, not prediction, drives it.
- How do you keep an evolutionary approach from decaying into no architecture at all?Encode the architectural characteristics as automated fitness functions (dependency and cycle rules, latency and error budgets, coupling limits) that run in CI, keep an ADR trail for one-way-door decisions, and reserve up-front design for those. Structure becomes enforced continuously rather than decreed once.
- What is the "last responsible moment" and how is it different from procrastination?It is the point after which delaying would eliminate an important alternative — so you deliberately gather information until then, and decide before options close. Procrastination has no such trigger and no information-gathering; it lets the decision be made by default when the choices have already narrowed.
Renting a flat instead of buying one, while keeping your deposit liquid: you haven't bought a house in every city you might move to (speculative features); you've kept the ability to move cheaply (options). The equivalent investments in software are tests, migrations and small contracts — they don't decide the future, they keep it affordable.