The Paradox: Every System Has the Same Problems
Open in the interactive app: practice, quiz, mock interview →In one minute
- Every team believes its domain is special, and it is: the vocabulary, the regulations and the cost of a mistake differ. Underneath, the same few problems repeat: contention, duplicates, ordering, fan-out, hot spots, overload, partial failure, multi-step consistency, finding things fast, and trust.
- The paradox: the more domains you see, the fewer distinct problems there seem to be. A seat at a concert, a driver at an airport and the last pair of shoes in stock are one problem with three costumes.
- Naming the shape is the move that turns a scary new problem into a familiar one, with decades of known solutions, failure modes and trade-offs attached.
- The domain still matters, but it decides the parameters (how long a hold lasts, what a duplicate costs, who gets told), not the mechanism.
- In the AI era this matters more: an agent can write the mechanism in minutes once you name it correctly, and it will confidently write the wrong one if you don't.
How it works
Why domains look different
A payments engineer talks about authorisations, captures and chargebacks. A ride-hailing engineer talks about dispatch, ETAs and surge. A chat engineer talks about presence, read receipts and fan-out. Each group has its own conferences, its own jargon and its own war stories, so it is natural to assume each needs its own way of thinking.
Strip the nouns off the war stories and they rhyme. "A customer was charged twice" and "a rider got two cars" and "the group got the message twice" are the same sentence: a request was retried, and the side effect was not idempotent. Payment providers say the same thing in their own docs: a connection error leaves you not knowing whether the request ran, so the API accepts a client-chosen key to make a retry safe (Stripe API docs), and Stripe's webhook guide tells receivers to expect the same event more than once (Stripe webhooks). "We sold 103 tickets for 100 seats" and "two riders were matched to one driver" are the same sentence: two writers raced on one scarce thing and the check and the write were not atomic.
The ten shapes
This course uses ten shapes, listed in curriculum/problem-shapes.md and on the home page:
- Contention: many writers want one thing.
- Duplicates: the same request arrives more than once.
- Ordering: events arrive in a different order than they happened.
- Fan-out: one event must reach many receivers.
- Hot spots: skew, where one key gets far more than its share.
- Overload: demand exceeds capacity.
- Partial failure: you cannot tell whether something happened.
- Multi-step consistency: several changes must all happen or none.
- Finding things fast: search, lookup and nearest-neighbour.
- Trust but verify: checking that what happened is what should have happened.
The list is not sacred. What matters is having a short list in your head and reaching for it every time.
What the domain decides
Once the shape is named, the domain supplies the numbers and the policy. For contention, the mechanism is a hold with an expiry plus an atomic check-and-set. The domain decides how long the hold lasts (a concert seat: ten minutes while you pay; a driver: fifteen seconds while they accept; a stock unit: thirty minutes in a cart), what happens when the hold expires, and whether overbooking is allowed on purpose (airlines do it, cinemas do not). Mistaking a parameter difference for a mechanism difference is how teams end up building three reservation systems that each have a different bug.
Why this is the skill of the AI era
Coding agents are very good at producing a mechanism once it is named: "add an idempotency key with a unique constraint and return the stored response on conflict" is a ten-minute task. They are much worse at noticing that a feature request about "group gift cards" is secretly a multi-step consistency problem. The engineer who names the shape writes the right spec, asks the agent for the right mechanism, and knows which tests prove it. The engineer who doesn't gets fast, plausible code for the wrong problem.
Down the stack
Layer 1The contract
The human practice promises one thing: before designing or debugging, you can state the problem as "this is a shape problem in the domain domain, with these parameters". For example: "Booking a driver is contention on a scarce resource; hold 15 s; on expiry offer the next driver; never two confirmed riders per driver". The statement is testable: a reviewer can check that the design contains the known mechanism for that shape and a test for its known failure modes.
Layer 2The mechanism
Each shape maps to a small set of mechanisms with well-understood behaviour. Contention maps to locks, compare-and-set on a version column (UPDATE seats SET holder=$1, version=version+1 WHERE id=$2 AND version=$3), or a single-writer queue per resource. Duplicates map to idempotency keys stored under a unique index; the same idea lives inside Kafka, where an idempotent producer (since 0.11.0.0) gets a producer ID and sequence numbers so a resent batch does not duplicate log entries (Kafka design docs). Ordering maps to sequence numbers per key and partitioning by key so one consumer sees a key's events in order (Kafka orders messages only within a partition, and a key hashes to one partition, per the same page). Partial failure maps to timeouts, retries with idempotency, and reconciliation; the Azure Architecture Center warns that retrying a non-idempotent step, such as incrementing a value, gives a wrong result, and tells you to make each step idempotent (Azure: transient fault handling). Recognising the shape gives you this menu, and each item comes with known failure modes: locks deadlock, compare-and-set livelocks under heavy contention, unique indexes become hot.
Layer 3The machine
The same shapes appear at every level of the stack, which is the strongest evidence that they are real. Two CPU cores incrementing one counter are contention, solved by an atomic instruction (LOCK CMPXCHG on x86) that holds the cache line in exclusive state for a few nanoseconds. A TCP segment retransmitted after a lost ACK is a duplicate, solved by sequence numbers that let the receiver drop bytes it already has. Out-of-order packets are reassembled by the same sequence numbers. A NIC with more packets than ring-buffer slots drops them: overload, with the same answer as an API gateway (shed or queue). Hardware engineers solved these shapes in silicon decades ago; distributed systems engineers solve them again with network round trips that cost a million times more.
Why it's built this way
- Physics gives every system the same constraints: information takes time to travel, machines fail independently, and capacity is finite.
- Time to travel means two parties can act on stale views, which produces contention, ordering and duplicate problems.
- Independent failure means a sender cannot know whether a receiver acted, which produces partial failure and multi-step consistency problems.
- Finite capacity produces overload and hot spots, and the need to find things among many produces indexing problems.
- Because the constraints are universal, so are the problems, and so are the solutions. Only the parameters are local.
Diagram
flowchart LR D["New problem in your domain"] --> S["Strip the nouns"] S --> N["Name the shape"] N --> M["Known mechanisms"] N --> F["Known failure modes"] M --> P["Set domain parameters"] F --> T["Tests for each failure"] P --> B["Design or agent spec"] T --> B
Worked example
A product manager asks for "group gift cards": several friends each chip in, and the card is issued when the target is reached. It sounds new. Strip the nouns: many payers each make a payment (several steps), the card must be issued exactly once (duplicates), contributions can race at the threshold (contention), and if one contribution fails after others succeed, someone must refund (multi-step consistency).
So the design is three known mechanisms. Each contribution carries an idempotency key, so a payer who taps twice pays once. The running total lives in one row updated with compare-and-set, so the contribution that crosses the target is the only one that triggers issuance. Issuance and refunds run as a saga with compensations, driven by an outbox so that the "target reached" event is never lost. Compensations can fail and be retried, so each one must itself be idempotent, and some steps (an email already sent) have no undo and should come last (Azure: compensating transaction pattern).
With 50,000 gift cards a day and five contributors each, that is 250,000 payments a day, about 3 a second on average and maybe 30 at peak: trivial load. The difficulty was never scale; it was naming the shapes. A team that skipped that step would likely ship a check-then-insert race and a missing refund path, then rediscover both in production.
In the wild
- Twilio, 2013: duplicate recharges from a read-only billing store. After a network blip, a Redis master used for balances came back read-only with balances at zero. Auto-recharge logic then kept trying to top up accounts it could not update, so 1.4% of customers were charged repeatedly. Twilio refunded them with a 10% credit and added fail-safes that block charges when balances are missing or unwritable (Twilio post-mortem). A payments duplicate, in a telecom costume.
- Knight Capital, 2012: one server missing a deploy. New code reached 7 of 8 servers; the eighth ran retired order-routing logic that kept sending orders, producing over 4 million executions in 154 stocks in about 45 minutes and a loss near $460 million. The SEC found no second-person review of the deploy (SEC order). The same family: an operation that repeats without the sender noticing, plus no check that the rollout matched the intent.
- Nasdaq, 2012: the Facebook IPO cross. Orders entered into the opening cross hit 496,000 against a tested 40,000; a late cancel arrived during a 20 ms calculation and forced a recalculation that kept looping. This is overload meeting ordering, in an exchange instead of an app (SEC order).
Trade-offs
| Approach | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Name the shape first | Reuses proven mechanisms and tests; faster reviews | Can force-fit a genuinely novel problem | Almost always; most problems are not novel |
| Domain-first design | Captures domain rules and language precisely | Re-invents mechanisms, often with old bugs | Domain modelling, alongside shape naming |
| Copy a big company's architecture | Fast start, battle-tested ideas | Their parameters (scale, latency, team) are not yours | You genuinely share their constraints |
| Let the agent decide the mechanism | Very fast | Plausible code for the wrong shape | Prototypes and throwaway spikes |
Pitfalls in production
- Treating a parameter difference as a new problem, leading to three in-house reservation systems with three bugs. Fix: one shared component with domain parameters.
- Naming one shape and missing a second. Gift cards are duplicates and contention and multi-step consistency. Fix: run the whole list of ten for every new feature.
- Over-generalising: building a "universal workflow engine" because everything looks like a saga. Fix: share mechanisms, not giant frameworks; keep domain code in the domain.
- Assuming the agent knows the shape. Fix: put the shape and its failure modes in the spec, and ask for the tests first.
- Losing the domain's real constraints, such as legal rules on refunds, in the abstraction. Fix: parameters and policies stay explicit and reviewed by domain experts.
Leadership lens
- In design review, ask "which of the ten shapes is this?" before "which database?". It turns opinions into a checklist.
- Keep a team page that maps your domain's nouns to shapes: "booking = contention, webhook = duplicates + ordering". New hires and agents learn faster from it.
- Hire and promote for transfer: people who solved contention in logistics will solve it in fintech.
- When an incident review says "unique edge case", check whether it was a known shape with a missing mechanism. It usually was.
Check yourself
A rider app occasionally shows a trip as "cancelled" after it was "completed". Which shape is this, and what is the usual fix?
Show answer
Ordering: a late "cancelled" event overwrote a newer state. Attach a version or sequence number per trip and ignore events older than the stored version.
Why is "how long should a hold last?" a domain question, but "how do we stop two people holding the same seat?" is not?
Show answer
The atomic check-and-set mechanism is the same everywhere; the hold duration depends on how long the human or system on the other side needs, which only the domain knows.
A teammate says, "Our problem is unique, nobody has a real-time leaderboard for this game". How do you respond?
Show answer
Name the shapes: fan-out of score updates, hot spots on top players, finding things fast (top-k). Each has known solutions (sorted sets, sharded counters, periodic merge), so start there and focus effort on what is truly unique.
Why do these shapes also appear inside a CPU and in TCP?
Show answer
Because they come from universal constraints: delay, independent failure and finite capacity. Any system with those constraints, at any scale, meets the same problems.
Sources and further study
Primary
- Pat Helland, "Life beyond Distributed Transactions: An Apostate's Opinion" (CIDR 2007; ACM Queue version): entities as the unit of atomicity, and messaging between them.
Systems docs
- Stripe API: idempotent requests and Stripe webhooks.
- Apache Kafka design documentation: per-partition ordering and the idempotent producer.
- Azure: transient fault handling and compensating transaction.
Books
- Designing Data-Intensive Applications (1st ed.), Ch 8, "The Trouble with Distributed Systems", and Ch 9, "Consistency and Consensus" (ordering guarantees), by Martin Kleppmann.
- Release It! (2nd ed.), Ch 4, "Stability Antipatterns", and Ch 5, "Stability Patterns", by Michael Nygard.
- Design Patterns (1st ed.), Ch 1, "Introduction", by Gamma, Helm, Johnson and Vlissides: the idea of naming recurring solutions.
Other curricula
- System Design Primer: marks which HTTP methods are idempotent and notes that a queue such as SQS can deliver a message twice.
Watch
- You and Your Research (Richard Hamming): the 1995 talk on choosing important problems and carrying ideas between fields.
Free resources
- Making retries safe with idempotent APIs (AWS Builders' Library): the duplicates shape, solved the same way across many AWS services; a retry with the same client token returns an equivalent response, and a changed payload under that token is rejected.
- Designing robust and predictable APIs with idempotency (Stripe): how a payments company applies the same idea.
- Fallacies of distributed computing (Wikipedia): the universal constraints that produce the universal problems.
- Life beyond Distributed Transactions (Pat Helland): entities, activities and idempotence as domain-independent tools.
- How to Solve It (Wikipedia): Pólya's heuristic "have you seen this problem before in another form?".