AssumptionsGENERALCONTESTEDILLUSTRATIVE

Dangerous Assumptions

Some assumptions are silently load-bearing: one currency, one timezone, one warehouse, users have accounts, the provider is up, a request arrives once. Each is reasonable for V1, each is invisible in the code, and each turns a requirement change into a redesign. Knowing the list is how you notice yours.

The moveWorked exampleNext questions

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

Which assumptions are the ones that hurt — the ones that every V1 makes, that no code names, and that a single ordinary requirement turns into a redesign — and how do you find out which of them your system is standing on?

The situation

I have a register, I have been diligent, and I still got blindsided: the payment provider was down for an hour and the store queued nothing, retried nothing and told customers nothing. It never occurred to me to write "the provider is up" as an assumption. What else have I not thought to write down?

The reflex

Wait for the next one. Assumptions are discovered when they break, the register records them afterwards, and over time the list becomes complete. It is an honest position — you cannot list what you have not noticed — and it feels like the register is working.

Why it stalls

The assumptions that hurt most are the ones the register discovers last, because they are the ones that look like facts rather than choices. "Requests arrive once" is not something anyone decides; it is how the code reads until a retry makes it false. Waiting to notice means noticing in production.

What the reflex produces — and fails to produce
  • The assumptions that hurt most are the ones the register discovers last, because they are the ones that look like facts rather than choices. "Requests arrive once" is not something anyone decides; it is how the code reads until a retry makes it false. Waiting to notice means noticing in production.
  • Each of these is load-bearing in a specific, repeatable way, and the industry has broken every one of them enough times that the pattern is known. Rediscovering them one outage at a time is paying full price for information that is available for free.
  • The register fills with the assumptions you were able to see and misses the class of assumption that is invisible by construction — the ones that shaped what is *absent* from the code: no currency field, no timezone, no retry, no idempotency key, no warehouse entity.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Use the known list as a checklist against your own design, not as a lesson about other people's. There is a short set of assumptions that almost every first version makes, that no code names because each one makes something unnecessary, and that a single ordinary requirement contradicts: one currency, one timezone, one warehouse or region, users have accounts, the external provider is up, a request arrives exactly once, one language, prices do not change during a workflow, the clock is right, one tenant. For each, ask: does my system stand on this, and where?
  • Recognise the signature. A dangerous assumption shows as an absence — a bare number where a money type would be, a timestamp with no zone, a call to a provider with no timeout and no retry, a handler with no idempotency key, a product with no location. The absence is the assumption's footprint, and the checklist tells you which absences to look for (States That Must Be Unrepresentable).
  • Sort by what the contradiction costs. Some flip cheaply if isolated now — a money type at one chokepoint costs an hour and turns "second currency" from a redesign into a local change. Some cannot be isolated and must be designed for or explicitly deferred with a known consequence — "the provider is up" is either handled (External Systems Fail) or accepted as "the store is down when they are". The decision is which of these to isolate now, and it is not "all of them".
  • Record the ones you are standing on in the register with their absences as dependents (The Assumption Register), so that "sell in a neighbouring country" reads against "one currency, one timezone, one language" in a minute instead of being discovered field by field.

The checklist, as what each one breaks

Each row is one of the load-bearing assumptions, with the requirement that contradicts it, the footprint it leaves in the code, and the cheapest honest response. The table is a starting checklist, not a complete one; its purpose is to be walked against your own design, one row at a time, asking "am I standing on this, and where?".

Load-bearing assumptions and what contradicts them
TriggerSymptomCauseResponse
One currency → a second market, or a customer paying in another currencyTotals add unlike units; the provider is charged in the wrong currencyPrice is a bare number; nothing carries a currencyMoney as (amount, currency) at one chokepoint; refuse mixed sums; per-currency totals
One timezone → customers or staff in a second zoneSales start at the wrong hour; daily reports split daysTimestamps stored or compared without a zone; the server's day is "today"Store instants; decide the display and reporting zone explicitly (Time as a Dependency)
One warehouse or region → a second locationStock right in total, wrong per place; shipping from the wrong originNo location entity; stock as a columnInventory as (product, location); fulfilment rule as an explicit decision
Users have accounts → guest checkout, or an API clientCarts and confirmations have no homeEverything keyed by user id; notification path assumes an account emailCart keyed by token; contact captured at checkout — or a requirement, if identity is owed
The provider is up → an outage, a slow hour, a rate limitCheckout hangs or fails; nothing queued, nothing toldNo timeout, no pending state, no retry, no messageTimeout, pending payment state, retry with idempotency, customer-visible outcome (External Systems Fail)
Requests arrive once → a retry, a double click, a redelivered webhookDouble charge, double order, double emailHandlers with no idempotency key; side effects not keyedIdempotency key per request; unique constraint as backstop (Idempotency by Design)
Prices do not change during a workflow → an admin edit mid-checkoutCustomer pays a total that does not match what they sawOrder items reference the product's current priceSnapshot prices onto the order at checkout (Snapshots vs References)
One tenant → a second business on the same deploymentData leaks between tenants; reports mix themNo tenant key on any row; queries unscopedTenant on every row and every query, or a separate deployment (Multi-Tenancy)

Where the store stands, and what to do about each

The matrix is the store walked against the checklist. The footprint column is the specific absence; the last column is the sort — isolate now, design for, defer with consequence — and the deferred rows carry the consequence in words, because a deferral without one is a silent assumption reinstated.

AssumptionStanding on it?Footprint (the absence)Contradicting requirementSort
One currencyyesbare-number price; hard-coded currency in the provider callneighbouring country (already proposed)isolate now: money type at the pricing chokepoint
One timezoneyesraw timestamps; server day in reportssameisolate now: instants stored, zone decided per customer
One warehouseno longersecond location (done)retired; replacements registered
Users have accountsyescart keyed by user id; account email for confirmationsguest checkoutrequirement — legal identity; negotiate, do not drop
Provider is upyesno timeout, no pending state, no retry, no messageany outage (already happened)design for now
Requests arrive oncepartlyno idempotency key on confirmation; unique constraint onlyany retry (already happens)design for now: key first, constraint as backstop
One languageyeshard-coded text in templatessecond marketdefer; consequence: a pass over every template
Clock is rightyessale start compares server timea drifted clockdefer; consequence: a sale may start seconds off

"We need multi-currency support"

The stakeholder's sentence arrives as a feature and hides a much smaller need. The ladder walks it down to what a second currency actually requires of the store today, names the cheap thing that meets it, and — because the device is not "big features are always wrong" — says when the full feature was the right call.

From the feature request to the assumption it contradicts

We need multi-currency support before we sell in the neighbouring country.

  1. Why multi-currency? Customers there pay in their own currency and the store only knows one.
  2. Why does the store only know one? Price is a bare number and the provider call hard-codes the currency — the "one currency" assumption's footprint.
  3. Why is that a problem for one more country? Because a second currency in the same tables would corrupt totals and reports, and the provider would be charged in the wrong unit.
  4. Why does that require conversion, per-market pricing and multi-currency settlement? It does not. It requires that a second currency cannot be mixed with the first — which is a money type, per-currency totals and the currency passed to the provider.
real requirement A second currency must not corrupt totals, reports or provider charges — the "one currency" assumption must be fenced, not removed.
simpler Money as (amount, currency) at the pricing chokepoint, refuse mixed sums, per-currency totals in reports, currency passed through to the provider. Prices are still entered once per currency; nothing converts.

the claim was right when The business will price per market from a single base price, display converted prices, or settle in several currencies with finance reconciling exchange differences — then conversion rates, their staleness and their reconciliation are real requirements with an owner, and the full feature is the honest answer.

How to do it

Most important first.

  • Walk the checklist against your design and write, for each entry, where the system stands on it — the specific absence: which field, which call, which handler.
  • For each you stand on, name the ordinary requirement that would contradict it. If you cannot imagine one within the life of the system, record it and move on.
  • Sort into three: isolate now (cheap to fence, likely to flip), design for now (cannot be fenced; money or availability), defer with a stated consequence (unlikely or affordable).
  • For "requests arrive once" and "the provider is up", do not defer without saying what the customer sees when they are false; those two are the ones that cost money (Duplicate Requests).
  • Add your own entries to the checklist the first time one of yours breaks; the industry list is a start, not a ceiling.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • The store against the checklist. One currency: standing on it — price is a bare number, totals add, the provider call hard-codes a code. One timezone: standing on it — raw timestamps, the daily report. One warehouse: was standing on it, retired. Users have accounts: standing on it, but promoted to a requirement with a legal reason. Provider is up: standing on it — the charge call has no timeout, no retry, no "pending" state, and the outage proved it. Requests arrive once: standing on it — the confirmation handler has no idempotency key, and only the unique constraint from Invariants in an Online Store saved it. Clock is right: standing on it — sale start compares server time to an entered time.
  • Sorted. Isolate now: currency (a money type at the pricing chokepoint; an hour) and timezone (store instants, display per customer; an afternoon). Design for now: provider up (timeout, a pending payment state, a retry with idempotency, and a customer message — the outage already paid for this lesson) and requests arrive once (idempotency key on the confirmation handler; the constraint was the last line, not the first). Defer with consequence: one language (the store is in one market; consequence written: "all text is hard-coded; a second language is a pass over every template") and clock (the server syncs time; consequence: a sale may start seconds off).
  • "We need multi-currency support" from a stakeholder, walked down the why ladder in the section below: what was needed was that a second currency does not corrupt totals — which the isolation step delivers — not that the store converts, prices per market or settles in several currencies. The ladder found the cheap version of the requirement, and the case where the full version is right.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • For every entry on the checklist you can say where your system stands on it, as a specific absence — or that it does not.
  • Each one you stand on is sorted: isolated, designed for, or deferred with a written consequence.
  • The two that cost money — provider is up, requests arrive once — are not in the deferred pile without a stated customer-visible consequence.
  • The register gained entries you had not noticed, and their dependents are absences rather than code.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?For each of one currency, one timezone, one region, accounts required, provider up, requests arrive once, one language, one tenant — does my system stand on it, and which absence is the footprint?
  • ?What ordinary requirement would contradict each one within the life of this system?
  • ?Which are cheap to isolate now, which must be designed for, and which can be deferred with a consequence I have written down?
  • ?When the provider is down or a request arrives twice, what does the customer see — and have I decided that or defaulted it?
  • ?Which assumption is my system standing on that is not on anyone's list yet?

What can go wrong

How the move itself fails
  • Every checklist entry is isolated pre-emptively: a money type, a locale type, a tenant type, a region type, each with one implementation, in a store with one market. The checklist is for noticing; the sort is for deciding, and "defer with consequence" is a legitimate outcome.
  • The checklist replaces thinking: the ten entries are checked and the store's own dangerous assumption — "a product has one price" for a B2B tier that is already being discussed — is missed because it was not on the list.
  • Assumptions are isolated with abstractions that do not actually fence them. A Money type whose currency is always the same constant has not isolated anything; the test is whether a second value could flow through without touching every consumer.
What the move costs
  • Isolating an assumption that never flips is an abstraction paid for nothing; the checklist raises the temptation to fence everything.
  • Designing for "the provider is down" adds a pending state, a retry and a customer message to a checkout that was three steps — real complexity, justified by an outage that has already happened or by money at stake.
  • The checklist is industry-general and a domain has its own; using only the general list produces confidence that is slightly misplaced.
Misreads
  • "Avoid these assumptions." Make them — they are why V1 ships. The lesson is to know which ones the system stands on, what the footprint is, and which to fence. A V1 store with a money type and one currency is fine; a V1 store that does not know it assumed one is the problem.
  • "The provider being up is a reliability concern, not an assumption." It is both. As an assumption it has dependents — no timeout, no pending state, no message — and a false-when; naming it as one is what puts it in front of the design instead of in front of the on-call engineer (When the Provider Fails).
  • "If the register is complete, the checklist is redundant." The register holds what you noticed; the checklist is a list of what people reliably fail to notice. They are complementary, and the checklist is how the register gets the entries that would otherwise arrive as outages.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALThe checklist entries recur across almost every product-shaped system because they are all consequences of building for one market, one deployment and one happy path first; a pipeline or a compiler has its own analogues — one encoding, one input file, one machine — and the same move applies.
  • CONTESTEDA strong school holds that fencing assumptions before they flip is the definition of over-engineering: a money type in a one-currency store is speculative generality, YAGNI applies, and the honest estimate for adding a currency later is usually smaller than the accumulated cost of abstractions that were never used. The strongest version of that view is that most of these assumptions never flip in most systems, and the ones that do are cheaper to handle when the requirement is concrete than to anticipate in the abstract. The position here is narrower than "fence them all": isolate the cheap and likely ones, design for the two that cost money, and defer the rest with a written consequence — which is a judgment made per system, and the other school's point is why the deferred pile exists.
  • ILLUSTRATIVEThe store's outage, the hour for a money type and the afternoon for timezones are invented to show the sort; real costs depend on how many consumers the absence has, and the checklist itself is a starting set rather than a canonical ten.

Where the depth lives

This domain asks the question and hands the answer off by name.

Further
  • The technology decision tool at /thinking/decide asks "which problem are you solving?" of every component; the same question, asked of every assumption on this list, is the sort in the matrix above.