SystemsGENERALSCALE-SPECIFICILLUSTRATIVE

Who Owns This State?

For each piece of information in the system, one component should be allowed to change it and everyone else should ask. Assigning an owner to every fact — cart contents, stock, payment status, read state — is how a system stays consistent without a coordinator.

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

For every piece of state the system holds, which component is allowed to change it, and what happens when two of them think they are?

The situation

The cart lives in the browser's local storage, and also in a carts table, and the checkout service has its own copy it validated against. When the customer opens a second tab the three disagree, and every fix so far has been "sync it again at another point", which has made it worse. I do not know where the cart *is*.

The reflex

Sync harder. Add a write whenever anything changes, in every direction, so all three copies are always up to date. The code for that is straightforward, and each additional sync point fixes the last reported case.

Why it stalls

Every sync point is a race. Two tabs each sync their copy, the last write wins, and an item the customer removed comes back. The fix for that is another sync, which is another race; the code grows and the number of inconsistent states grows with it.

What the reflex produces — and fails to produce
  • Every sync point is a race. Two tabs each sync their copy, the last write wins, and an item the customer removed comes back. The fix for that is another sync, which is another race; the code grows and the number of inconsistent states grows with it.
  • Nobody can say what a correct cart is, because there is no single thing that is the cart — only three things that are supposed to be equal. So there is no test for "the cart is right", only tests for "copy A equals copy B", which pass while the customer sees the wrong thing.
  • When checkout finds the total differs from the cart page, it cannot tell which copy is stale, so it picks one. The choice is made in a validation function nobody thinks of as a design decision, and it decides who gets charged what.
  • The same pattern is being built for stock (browser badge, product table, checkout check) and for read state in the chat app, because "keep copies in sync" was learned as the technique rather than "decide who owns it".
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

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

  • Enumerate the state — every fact that changes over time — and for each ask: which component is allowed to *decide* its new value? That component is the owner. Everyone else may hold a copy for display or speed, and must send changes to the owner rather than applying them locally as truth.
  • Test each candidate owner against the invariants the fact carries. The cart's invariant is "the customer gets charged for what they see"; the owner has to be the thing checkout reads, which rules out the browser. Stock's invariant is "never sell more than exists" under concurrency; the owner has to be somewhere that can serialise decrements, which rules out the service's memory.
  • Once the owner is named, every other copy becomes a *view* with a known staleness: the browser shows the cart it last received, and every mutation goes to the owner and comes back. The syncing code disappears, replaced by one direction of flow.
  • For state whose owner is outside the system — payment status, delivery — the same rule holds and your copy is a view; the difference is that the owner does not push, so the view needs a policy for how it is refreshed (Source of Truth).

State, invariant, owner

The table is the output of the move. The middle column is what does the choosing: the owner is whichever component can enforce the invariant while performing the change. Read down the last column and notice that every view knows it is a view and how it is refreshed — that knowledge is what the syncing code lacked.

StateInvariantOwner (decides)Views (display), refreshed by
Cart lines and quantitiescharged for what was seen at Buyserver-side cart recordbrowser cart, from the owner's response to every mutation
Stock per productnever oversell under concurrencyinventory row, in a transactionproduct badge (stale allowed), checkout re-asks the owner
Order statuslegal transitions onlyorder record, via its state machineorder history page, admin list — on load
Payment statusreflects the provider's chargethe payment providerOrder.paymentStatus, from webhooks and a re-check on read
Read position (chat)never moves backwards per userserver per-user-per-conversation recordeach device, sends position and receives the max
Search box text, focused tabnone that the server cares aboutthe browsernone — the server never sees it

The cart, before and after

Before: three holders, each written to, arrows in every direction, and the last write wins. After: one owner, views that only render what the owner returned, and mutations that flow one way. The diagram after has fewer arrows, and every one of them has a direction.

add / remove (mutation)resulting cartadd / remove (mutation)resulting cartreads in the order transactiondecrements, serialisedTab A (view)CheckoutCart owner (server record)Inventory row (owner of stock)Tab B (view)
UserLLMAgentToolDataDecisionHumanGuardrail

What each wrong owner does

The failure table is the argument for choosing by invariant. Each row is a plausible owner that a diagram might have picked, and the symptom that owner produces under exactly the condition its invariant was about.

TriggerSymptomCauseResponse
Browser owns the cart; customer opens a second taban item removed in one tab reappears from the othertwo owners, last write wins, no invariant enforced anywhereserver record owns the cart; tabs render what it returns
Checkout service memory owns stock; two instancesthe last unit sold twice on a sale dayeach instance decrements its own countthe inventory row owns stock; decrement in the transaction
Order record owns payment status; provider reverses the chargeorder stays "paid" and shipsthe fact is decided outside; the record was treated as ownerprovider owns it; store a view with a timestamp and re-check before shipping
Each chat device owns its read positionunread count flickers between phone and laptoptwo devices each push their own value as truthserver owns the position and applies the max

How to do it

Most important first.

  • List the state, not the tables: cart contents, cart total, stock per product, order status, payment status, "which messages has this user read". Include state that only lives in the browser.
  • For each, name the invariant it carries and the operation that changes it. The owner is whichever component can enforce the invariant while performing the operation (What Must Never Break).
  • Write the owner and the views down as a table. Every arrow between a view and its owner points one way: mutations in, snapshots out.
  • Find every place a view is written to as if it were the owner, and remove that write. This is where the syncing code goes, and it is the part that feels like deleting a feature.
  • Where the owner is external, decide the refresh policy for the view: webhook, poll, or re-check on read (Treating External Systems as What They Are).

Worked on a concrete problem

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

  • The cart. State: the lines and their quantities. Invariant: what the customer is charged for is what they saw when they pressed Buy. Owner: the server-side cart, because checkout reads it and it can be read inside the same transaction that creates the order. The browser copy becomes a view: add-to-cart sends a mutation, receives the resulting cart, renders it. Two tabs now show the same cart after any action because both render what the owner returned. The local-storage copy is kept only for a guest's cart before they have a server-side one — a decision written down rather than accidental.
  • Stock. State: units available per product. Invariant: never sell more than exists, under concurrent checkouts. Owner: the inventory row in the database, because it is the only place two decrements can be serialised (Invariants Under Concurrency). The product page badge is a view that can be stale by design; checkout does not trust it and asks the owner inside the transaction.
  • Read state in the chat app. State: the last message each user has read in each conversation. Invariant: a user's own read position never goes backwards. Owner: the server's per-user-per-conversation record, because two devices for the same user both send "read up to here" and someone has to take the max. Each device holds a view and sends its position; neither device is the truth, which is why the unread badge stops flickering.

How you know it worked

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

  • Every piece of state has exactly one owner in the table, and you can say the invariant that made you choose it.
  • The syncing code has been deleted rather than extended, and the flow between a view and its owner is one direction each way.
  • "Which copy is right?" no longer occurs as a question, because there is one copy that decides and the rest are labelled views.

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
  • ?What state does this system hold, including the state that only lives in the browser or in a worker's memory?
  • ?For each piece of state, what invariant does it carry, and which component can enforce that invariant while changing it?
  • ?Which copies of this state are views, how stale may each be, and how does each learn about changes?
  • ?Where does this code write to a view as though it were the owner?

What can go wrong

How the move itself fails
  • The owner is chosen for architecture reasons — "the cart service owns carts" — before the invariant is known, and it turns out to be the wrong place to enforce it. Owners are chosen per fact from the invariant, not per service from a diagram.
  • Views are forbidden. The browser is told it may not hold a cart at all, and every keystroke round-trips. The move allows views; it forbids views that decide.
  • The owner for a single fact is split by field: the browser owns quantity and the server owns price. Two owners for one thing is two things pretending to be one (Source of Truth).
  • Ownership is assigned and then enforced only by convention. If any code can still write to the view as truth, it eventually will; the enforcement is that views are read-only types in the code that holds them.
What the move costs
  • A single owner is a single place every mutation has to reach, which is a round-trip for state that used to be changed locally; the browser cart feels slower unless the view updates optimistically and reconciles.
  • Choosing the database as owner for stock means the invariant is enforced by a transaction, which serialises exactly the operation that is most contended at a sale; the alternative designs are the ones the distributed-systems domain teaches, and they cost more.
  • External owners mean your views are stale by an amount you do not control, and every screen that shows them needs a "last checked" story.
Misreads
  • "The backend owns all state." The backend owns the state whose invariants it enforces. Which tab is focused, what the customer has typed into the search box, and an unsent draft are owned by the browser, and pushing them to a server would be the same mistake in reverse (Who Owns This State?).
  • "An owner per fact means a service per fact." Ownership is a design decision about which code may decide; the cart owner and the stock owner can be the same process and the same database. Splitting services is a separate question with separate costs.
  • "Views must never be stale." A view is stale by definition; the design question is how stale it may be for this fact, and whether the reader is told. The product badge may be seconds behind; the checkout total may not be.

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.

  • GENERALAny state with more than one holder needs an owner, in a browser app, a service, a pipeline or a distributed cache. The invariant test for choosing the owner is the part that transfers.
  • SCALE-SPECIFICIn a single-process app the owner is a module and enforcement is a type; across services the owner is a service and every view is a network hop with its own staleness, and the question "how does the view learn about changes?" becomes the bulk of the design.
  • ILLUSTRATIVEThe three-copy cart, the two tabs and the flickering unread badge are invented to show the shape of the problem; the ownership assignments are one reasonable design for a small store and chat app.

Where the depth lives

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