The Cost of Change
Design decisions are bets on which changes arrive. The bet is priced in indirection now against edits later, and it can lose.
The requirement, the obvious build, and why it breaks
Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind survives until the requirement changes.
How do I decide whether a structural change is worth making, before I know which requirements are coming?
A senior engineer wants to spend a week introducing an abstraction "so the next feature is easy". Nobody can say which next feature.
Good design is always worth it. Take the week; you will get it back.
You get it back only if the anticipated change arrives. Abstractions built for changes that never come are pure cost, and they are the most common form of over-design there is (Speculative Generality).
- You get it back only if the anticipated change arrives. Abstractions built for changes that never come are pure cost, and they are the most common form of over-design there is (Speculative Generality).
- Worse, a structure built for the wrong axis of variation makes the change that *does* arrive harder, because it now has to be threaded through machinery designed for something else.
- The week is not the only cost. Every engineer who reads the code afterwards pays a small tax in indirection, forever, and that tax is invisible in any estimate.
- "Always worth it" is unfalsifiable, and a claim that cannot lose is not engineering judgement.
What limits the solution, and what must never stop being true
This domain leads with these two. A design that ignores its constraints is not a design, and an invariant nobody named is one nothing is protecting.
- The week is real and comes out of committed delivery.
- Nobody has a reliable roadmap beyond the current quarter.
- The team is five people, so a structure only pays if all five find it obvious.
- Whatever structure is chosen, current behaviour must not change — a refactor that alters behaviour is not a refactor (What Refactoring Actually Is).
Who owns what, and where the seams fall
Responsibilities decide boundaries; boundaries decide what an interface has to say.
- Whoever proposes the structure owns naming the specific change it makes cheap.
- Whoever approves it owns the counterfactual: what happens if we do nothing and the change arrives anyway.
- The decision boundary is the axis of variation. A structure buys flexibility along exactly one axis and pays for it along every other (Choosing the Model).
- Deciding which axis is the entire decision, and it is a question about the domain, not about code.
The bet, written out
A structural decision has four outcomes, not two, and teams usually argue about only one of them. Writing all four down takes about a minute and changes the conversation, because the disagreement is almost always about the probability rather than about the design.
- The two cells on the diagonal are the ones being argued about; the other two are what makes it a decision rather than a preference.
- The bottom-left cell is the honest cost of YAGNI, and pretending it is zero is how "just do it later" becomes a crisis.
- The top-right cell is the honest cost of "good design is always worth it", and it is paid quietly by everyone who reads the code afterwards.
| The change arrives | The change never arrives | |
|---|---|---|
| Built the structure | The change is cheap. This is the outcome the proposal is arguing for, and it is real. | Permanent indirection tax on every reader, plus the week. The most common outcome, and the one nobody plans for. |
| Did not build it | Refactor under deadline pressure, then make the change. Genuinely worse than having built it — this is the cost YAGNI ignores. | Nothing was spent. The code stayed as simple as the problem. |
What makes a bet worth taking
Three things move the arithmetic, and none of them is how elegant the structure is. How likely the change is; how much more expensive it gets if you retrofit rather than build now; and how reversible the decision is if you turn out to be wrong.
Reversibility is the one that gets least attention and deserves most. A decision you can undo in an afternoon can be made quickly and cheaply on weak evidence; one that shapes every module has to clear a much higher bar, because being wrong is expensive in a way the first is not.
What specific change does it make cheap, and how likely is that change?
when A second payment provider at a payments company; a persistence seam in a system that will outlive its database.
cost Build it now. The cost is the indirection tax and the week, and the expected saving clearly exceeds both.
when A second export format, a second notification channel.
cost Wait for the second case. It costs one deferred refactor and buys you the actual shape of the variation instead of a guess (The Rule of Three).
when Nobody can say what it makes cheap.
cost Do not build it. This is the definition of speculative generality, and it is the single most common way codebases acquire structure nobody can remove (Speculative Generality).
when Multi-tenancy, an id scheme, an audit trail, timezone handling.
cost Often build a minimal version now. These are the genuine exceptions: not because the change is likely, but because retrofitting them touches every row of stored data.
when A plugin API a customer is paying for; a public library boundary.
cost Not a bet at all — it is the feature. Design it as one, with the versioning obligations that implies (Plugin Architecture).
Deciding, and deciding again
The decision is worth writing down, briefly, with the thing that would change your mind. Not for process reasons — because in eighteen months the structure will look either obviously right or obviously stupid, and in both cases nobody will remember what was known at the time.
The revisit trigger is the part that matters and the part usually omitted. A decision with a trigger can be reopened by evidence; one without becomes something the team believes.
1# Single payment provider, no gateway abstraction2 3Context One provider (Stripe). Five engineers. No roadmap commitment4 to a second. Checkout calls the SDK directly in 3 places.5 6Decision No PaymentGateway interface for now. Call the SDK directly,7 but keep the calls inside billing/ so they are findable.8 9Why Nobody can name a second provider or a date. Retrofit cost is10 ~3 days, contained in one module. The interface would have11 exactly one implementation, which teaches us nothing about12 what varies.13 14Cost If a second provider arrives under deadline, we pay ~3 days15 plus the feature, at the worst moment.16 17Revisit A second provider is named by anyone with budget authority,18 OR SDK calls appear outside billing/.The last line is the one that does the work. Without it this is an opinion; with it, it is a decision that a future engineer can act on without relitigating the original argument (Decision Records).
How to build it
Most important first.
- Name the change. "So the next feature is easy" is not a justification; "so we can add a second payment provider without touching checkout" is, because it can be wrong.
- Ask how likely it is, honestly. A second payment provider in a payments company is near-certain; a plugin system for a tool with three internal users is not (Extensibility).
- Ask what it costs if you are wrong in each direction: build it and it is not needed, versus skip it and it is. Those two are rarely symmetric, and the asymmetry usually decides.
- Prefer the change that is cheap to reverse. A structure you can undo in a day is a small bet; one that shapes every file is not (Reversible and Irreversible Decisions).
- Write down the trigger that would make you revisit, so the decision has an expiry rather than becoming folklore (Revisit Triggers).
What the next change costs
The field this whole domain exists for. A structure is only better if it makes the change after this one cheaper — and it is worth saying which changes it does not help.
- Doing nothing: the anticipated change, if it comes, costs the refactor plus the change, done under deadline pressure rather than calmly. That is genuinely worse than doing it now — but only in the branch where it comes.
- Doing it now: every unrelated change pays a small indirection tax immediately, and the anticipated change becomes cheap if it arrives.
- The expected cost is the probability times the saving, minus the certain tax. Most teams do not do this arithmetic even roughly, and doing it roughly is a large improvement on not doing it.
- Deferring structure means occasionally paying the refactor under deadline pressure. That is a real cost and the honest counter-argument to YAGNI.
- Demanding a named change before allowing abstraction slows down engineers who are usually right, and some of that friction is wasted.
What can go wrong
- The abstraction is built, the anticipated change never arrives, and it is never removed because removing it is also work.
- The abstraction is built along the wrong axis, and the real change has to be forced through it, producing something worse than either option.
- The decision is made by seniority rather than by argument, so nobody can revisit it without it being personal (Tone, Disagreement and Receiving Review).
- A speculative abstraction creates a dependency from real code onto a hypothetical requirement, which is the worst dependency direction available.
- Every consumer of the new structure becomes a reason it is hard to remove later, so adoption is itself a cost.
- "So never abstract early." Sometimes the second case is obviously coming and the cost of retrofitting is enormous — a persistence boundary in a system that will certainly outlive its database is a reasonable early bet.
- "YAGNI means no design." YAGNI is about speculative *features and flexibility*, not about giving the code coherent responsibilities. Those are different activities (YAGNI, With Its Bill Attached).
- "We can estimate this precisely." You cannot. The point of the arithmetic is to make the assumption visible, not to produce a number.
Testing it, and how it ages
- A refactor with no behaviour change needs a test suite that would notice a behaviour change; without one, the "refactor" is an unverified rewrite (Refactoring Without Tests).
- Test at the boundary you are keeping stable, not at the internals you are about to move, or the tests become the thing blocking the refactor (What a Unit Is).
- Good decisions expire. A structure that was right at five engineers and one product line can be wrong at fifty and four (Revisit Triggers).
- The cheapest time to add a structure is when the second case actually appears, because you can then see both cases and the shared shape is evidence rather than a guess (The Rule of Three).
Where this applies
This domain's advice is contested more than most. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view rather than a caricature.
- GENERALThat a structure is a bet on future change, priced in present indirection, holds for any codebase that will be modified after it is written.
- CONTESTEDThe strongest opposing case: some structures are so much cheaper to build early than to retrofit — a persistence seam, an effect boundary, an id strategy — that expected-value reasoning underrates them, because retrofitting is not merely expensive but often politically impossible once the codebase is large. That argument is sound for a small number of decisions and is routinely used to justify a much larger number.
- SCALE-SPECIFICAt five engineers a wrong structure is a week to undo; at fifty it is a quarter and a migration, so the same bet is worth taking at one size and not the other. Advice from large-company engineering blogs is frequently wrong for small teams for exactly this reason.
Where the depth lives
This domain teaches the codebase-level structure and hands the rest off.
- — System Design — the same expected-value reasoning governs capacity and redundancy decisions, where the asymmetry between over- and under-building is usually much starker.