The Design Loop
Requirement, constraints, invariants, responsibilities, boundaries, interfaces, state, dependencies, failure, implementation, tests, feedback, evolution — in that order, because each answer constrains the next.
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.
What is the general shape of a design decision, so I can ask the same questions of any feature?
Someone asks for a feature. Before any code is written, there is a set of questions whose answers determine everything that follows.
Start coding. The design emerges from the code, and thinking up front is waterfall.
Design does emerge — but the parts that emerge last are the parts that are most expensive to change: state ownership, boundaries, and the shape of the interface everyone has already called.
- Design does emerge — but the parts that emerge last are the parts that are most expensive to change: state ownership, boundaries, and the shape of the interface everyone has already called.
- By the time the code exists, several decisions have been made implicitly and are now defended by the code that depends on them.
- The failure is specific and recognisable: the happy path is written first, and error handling, concurrency and migration get retrofitted into a structure that had no room for them (Designing the Happy Path Last).
- It also makes review useless. A reviewer looking at a finished implementation can catch bugs but cannot realistically ask for a different boundary (Review as Design Feedback — and Why It Arrives Too Late).
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 loop has to be cheap enough to run for a small feature, or it will only ever be run for large ones — where it is least likely to help, because large features are already reviewed.
- The order matters: an answer taken out of order is a guess. You cannot choose a boundary before you know the invariant it protects.
Who owns what, and where the seams fall
Responsibilities decide boundaries; boundaries decide what an interface has to say.
- The loop itself owns nothing; it is a checklist. Its value is that the omissions become visible, and omissions are what reviews miss.
- Each step names a decision someone has to own, so the loop also allocates responsibility.
- Boundaries sit in the middle of the loop, and that placement is the argument: they come after invariants and responsibilities because those decide where a boundary should fall, and before interfaces because a boundary decides what an interface must expose (Architecture Boundaries).
Thirteen steps, and the question each one asks
Read this as a set of prompts rather than a process. The useful moment is the one where you cannot answer, because that is a decision you were about to make by accident.
- 1Requirement
What does someone actually want, in their words?
fails by Building the mechanism the ticket described rather than the outcome someone needed.
- 2Constraints
What limits the solution — team, deadline, existing system, compliance?
fails by A design that would be right in a different company (Constraints Are Part of the Design).
- 3Invariants
What must never stop being true?
fails by The rule ends up enforced in three places and guaranteed in none (Where Invariants Live).
- 4Responsibilities
Who owns each piece of this?
fails by A class that accumulates every reason to change (God Object).
- 5Boundaries
Where should the seam fall so a likely change is contained?
fails by Boundaries drawn by technical type, so every feature crosses all of them (Package by Layer).
- 6Interfaces
What must a caller know, and what can we keep hidden?
fails by An interface shaped by the implementation, which then cannot change (Designing a Module Interface).
- 7State
What state exists, who may mutate it, and what are its legal transitions?
fails by Four booleans encoding six impossible combinations (Boolean Flag Explosion).
- 8Dependencies
What does this depend on, and is the direction right?
fails by Policy depending on a volatile detail (Dependency Direction).
- 9Failure modes
What happens when the dependency is slow, absent, or answers twice?
fails by Error handling retrofitted into a structure with no room for it (Failure-Aware Feature Design).
- 10Implementation
Write the smallest thing that satisfies the above.
fails by Being the first step rather than the tenth.
- 11Tests
What is tested, at which boundary, and why that one?
fails by Tests that mirror the implementation and block every refactor (Mocking).
- 12Feedback
How will we know in production whether this works?
fails by A feature that can only be debugged by reading the code (Debuggability by Design).
- 13Evolution
What will the next change to this cost?
fails by Never being asked, which is how change amplification accumulates unnoticed.
The order is not arbitrary. Invariants decide boundaries; boundaries decide interfaces; interfaces decide what can be tested in isolation. Answering out of order means guessing.
Where the loop actually earns its keep
Three steps repay the whole exercise, and all three are ones an implementation-first approach reaches last: invariants, failure modes and evolution. They share a property — each is cheap to decide now and expensive to retrofit, because each shapes the structure rather than sitting inside it.
The others are useful mostly because they force these three to be reachable.
- — How a price is calculated
- — Which table orders live in
- — The email template for confirmations
- — Which payment provider is configured
- — What an order status string means
- — Validates input
- — Computes totals
- — Writes to the database
- — Calls the payment provider
- — Sends email
- — Writes an audit record
- — Database
- — Payment SDK
- — SMTP client
- — Template engine
- — Clock
- — Config
- — Pricing rules change
- — The schema changes
- — The email copy changes
- — The payment provider changes
- — A new order status is added
- — The audit format changes
- — Anything about validation changes
Seven distinct reasons to change is the finding. Any one of them forces a redeploy and a full regression of the other six, and every one of those seven teams now queues behind the same file. The fix is not "split it into seven classes" — it is to notice that pricing, persistence and notification are three different rates of change, and to give the two most volatile ones their own address first.
How to build it
Most important first.
- Run it in order and out loud. Most of the value is in noticing which step you cannot answer.
- Timebox it to minutes for a small feature. A loop that takes an afternoon will be skipped, which is worse than a rough answer to each step.
- Write down the answers where the code lives, so the reasoning survives the author (Docs Close to Code).
- Treat "I do not know" as an answer that stops implementation rather than one you code past.
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.
- Running the loop costs minutes now against the possibility of a boundary you cannot move later. Its whole return is in the steps you would otherwise have answered implicitly.
- The steps that pay for it most reliably are invariants, failure modes and migration, because those are the three that are most expensive to retrofit and most likely to be skipped.
- Any checklist trades some autonomy for consistency, and experienced engineers legitimately resent that. The counter is that the loop is a prompt, not a gate.
- It biases toward deliberation, which is wrong for genuinely trivial changes and for prototypes that will be deleted.
What can go wrong
- The loop becomes a template that is filled in after the fact to satisfy a process, which produces documents and no thinking.
- It is run only for large features, so the small changes that quietly accumulate design debt are never examined.
- It is treated as a sequence to complete rather than as a set of questions to fail on — the point is to get stuck.
- Each step depends on the ones before it. Choosing an interface before knowing the state it manages produces an interface that leaks the implementation (Designing a Module Interface).
- "This is big design up front." It is thirteen questions, most answerable in a sentence. Big design up front is committing to an architecture before building anything; this is refusing to choose a boundary by accident.
- "Emergent design means no thinking." Emergent design means deferring decisions until you have evidence — which requires knowing which decisions you are deferring.
- "We do this in code review." Review happens after the boundary exists, and moving a boundary at review time is expensive enough that it almost never happens.
Testing it, and how it ages
- The
testsstep is inside the loop deliberately: deciding what to test at which boundary is a design decision, and deferring it until after implementation means the tests describe whatever got built (Testing as Design Feedback).
- The loop's last step is evolution, which feeds the next requirement back to the first step. That is the shape of a codebase that stays workable: every change re-enters at the top.
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.
- GENERALThe dependency order between the steps follows from what each decision needs as input, so it holds regardless of language, paradigm or process.
- LIFETIME-SPECIFICFor a spike or a prototype being deleted next week, most of the loop is waste and the honest answer is to skip it — but that decision should be made explicitly, because prototypes that ship are how most of this domain's problems begin.
Where the depth lives
This domain teaches the codebase-level structure and hands the rest off.
- — System Design — the same loop runs at a larger grain, where "boundaries" means services and "failure modes" means partitions rather than exceptions.