Flash Sale
Design a high-demand sale for a limited inventory item.
Interview scope and guarantees
Admit buyers, reserve scarce inventory, create orders, collect payment and show a durable outcome. Define fairness and per-user purchase limits. Approximate stock displays are acceptable; successful purchases must never exceed available stock.
Capacity worksheet
Assume 1 million arrivals in 10 seconds for 10,000 units: 100,000 requests/s at the edge. If inventory safely handles 2,000 reservation attempts/s, admission must shed or queue the excess. At most 10,000 units can be sold regardless of compute capacity; rejecting early is part of the design.
Concrete API contract
POST /sales/{id}/admissions -> {ticket,expiresAt}
POST /sales/{id}/reservations {ticket,quantity,requestKey}
POST /orders/{id}/pay {paymentToken}
GET /orders/{id}Data model and access paths
stock(sale_id,sku,available,reserved,sold,version); PK(sale_id,sku)
reservations(id PK,sale_id,sku,user_id,request_key,request_hash,quantity,state,expires_at); UNIQUE(sale_id,user_id,request_key)
orders(id PK,reservation_id UNIQUE,user_id,state)
purchase_limits(sale_id,user_id,held_qty,bought_qty); PK(sale_id,user_id)Evolve a solution and explain each change
Figure — Three architecture decisions for Flash Sale, including the pressure each introduces.
Step 1: Own stock authoritatively
Conditionally reserve units with unique customer/order identity. A displayed cached count cannot prevent overselling.
Step 2: Bound checkout admission
Use a waiting room, per-user limits, and short reservation deadlines. Admission capacity and reserved inventory are different quantities.
Step 3: Reconcile the checkout
Confirm matching reservations after payment and release expired holds once. Late payment can race with stock reassignment.
Responsibility overview
Figure — Connected responsibilities for Flash Sale. Trace the authoritative and derived paths separately.
Worked end-to-end scenario
There are 100 units, but 10,000 customers click buy. Admission allows a bounded set to attempt checkout; it does not allocate units. A conditional reservation consumes one unit and records hold h17 for customer C. C retries after a timeout and gets the same hold. Payment is delayed past expiry; the reservation is released and another customer owns the unit. A late success for h17 goes to reconciliation rather than decrementing stock again or stealing the new reservation. Publish remaining stock as an approximate browsing signal and use the ledger/reservation transaction for the promise.
Why these access paths matter
Store item stock and reservations with owner, quantity, state, expiry, and payment identity. Unique customer-plus-sale constraints enforce the stated purchase limit. Index expiring holds and pending payment attempts for repair. Release checks reservation state atomically so duplicate cleanup cannot add inventory twice. A distributed cache increment is not enough unless its durability and ownership model meet the stock contract.
Build the baseline first
- Buyer → admission gate → stock reservation transaction.
- Reservation → order and outbox → payment workflow.
- Payment outcome → confirm or release reservation.
Evolve the design under load
- Edge protection → signed queue tickets → bounded origin.
- Inventory token partitions → controlled allocation.
- Status cache → jittered polling → durable order state.
Defend the hardest decision
An atomic decrement with available >= quantity enforces nonnegative stock on one authority. Splitting inventory into token pools can distribute contention, but the sum allocated to all pools must not exceed the global inventory. Per-user limits must be checked at the same business boundary as reservation or reconciled with a clearly stated overshoot policy. A countdown cache is not a reservation ledger.
Failure and recovery analysis
Payment succeeds after reservation expiry. Reconcile against the current reservation state and refund when stock has been reassigned. Releasing inventory must be conditional and idempotent; duplicate expiry events cannot increment stock twice. After a service restart, rebuild sold and reserved totals from durable state and compare the stock conservation equation.
Security and privacy boundary
Rate-limit bots, validate signed admissions at the origin, and keep payment calls outside inventory locks.
Interview follow-ups with reasoning
Question: How do you stop token replay?
Show answer and explanation
Answer: Bind admission tokens to user, sale, expiry and a consumption record.
Question: How do you keep the waiting room fair?
Show answer and explanation
Answer: Define queue ordering and prevent repeated sessions from gaining extra positions.
Question: How do you test the invariant?
Show answer and explanation
Answer: Run concurrent reserve/pay/expire races and assert sold + reserved + available equals initial stock.
Operate and verify the design
Admission rejection, reservation conflicts, payment uncertainty and inventory conservation.
Replay reserve, pay and expire events out of order and assert that available + reserved + sold stays constant.
A second scenario to test transfer
A request arrives when one unit remains. Two regions both display a cached count of one, but the authoritative reservation condition allows exactly one decrement. The winner receives a reservation ID; the other receives sold out or a queue retry response. The public product page can lag slightly without changing the stock decision.
How can the service admit users gradually while still guaranteeing that 10,000 units are never reserved twice?
Show answer and explanation
Answer: Admission limits load, while a conditional inventory reservation or globally partitioned allocation enforces the cap. Use stable idempotency keys and one current reservation per purchase. If allocation is split by region, each region receives a disjoint bounded quota and the sum of quotas cannot exceed sale inventory.
Compare alternatives
| Layer | Purpose | Invariant |
|---|---|---|
| Waiting room | Shape arrival rate | Does not own stock |
| Inventory owner | Atomic reservation | Total reserved ≤ available |
| Payment workflow | Resolve external result | Late success is reconciled |
| Sale status view | Fast communication | May lag within stated bound |
A design-changing exercise
Can the waiting room guarantee that the first 100 admitted customers own the 100 units?
Show answer and explanation
Answer: Only if admission is explicitly coupled to reservation. Usually it controls load; authoritative inventory allocation is a separate conditional operation.
Design workshop: admission and inventory conservation
Core scope is one sale/SKU, a per-user quantity cap, temporary holds and payment. A shopping cart across unrelated merchants is excluded. Choose a cap of two units/user, a two-minute hold and zero oversold units. Waiting-room order is a product policy, not ownership of stock.
Stock has available,reserved,sold with available+reserved+sold=initial_total within the chosen adjustments ledger. Purchase limit authority stores held_qty and bought_qty under unique(sale,user). Stable request mapping is unique(sale,user,request_key) with fingerprint; retry returns the same reservation. Lock user limit and SKU stock in the same declared stable order for every command.
BEGIN;
SELECT * FROM purchase_limits WHERE sale_id=:sale AND user_id=:user FOR UPDATE;
SELECT * FROM stock WHERE sale_id=:sale AND sku=:sku FOR UPDATE;
-- First return an existing matching request result.
-- Require held_qty+bought_qty+requested<=2 and available>=requested.
UPDATE stock SET available=available-:q,reserved=reserved+:q WHERE ...;
UPDATE purchase_limits SET held_qty=held_qty+:q WHERE ...;
-- Insert reservation/request result and payment outbox together.
COMMIT;Ensure the limit row exists before locking it. Release and confirm lock the same rows and compare reservation state/identity. Only active→expired releases quantity; repeated expiry does nothing. Only active matching payment success→sold moves reserved to sold and held_qty to bought_qty. Late success after expiry becomes refund/unknown reconciliation; it cannot consume a newer user's hold.
Figure — Conservation for initial stock 10.
The waiting room issues signed sale/user/admission/expiry tokens. A durable one-use admission map associates a token with its checkout request; signatures alone do not prevent replay. Choose FIFO after sale opening with a published pre-opening lottery if desired. A user cannot reuse a token for unlimited parallel holds. Queue admission responds 202 with position/refresh interval, and reserve responds 201 or 409 sold_out/limit_conflict under a stable request result.
Figure — Expired hold and late payment preserve new ownership.
A global authority is simplest for a scarce sale. A regional quota extension gives disjoint durable stock pools whose sum equals sale inventory. Local reservations consume only their pool. Rebalancing is a fenced transfer: source freezes the transfer amount under a generation, durable coordinator records it, destination activates once under transfer ID, and source cannot reclaim ambiguous transferred stock. Never reallocate a disconnected region's unknown pool just because a lease expired; it may have sold units. Strand uncertain stock until reconciled rather than oversell.
At one million buyers arriving over ten seconds, arrivals are 100,000/s. If checkout can sustain 2,000 admitted/s, a queue drains that population in roughly 500 seconds before retries and withdrawals. A two-minute hold and provider throughput further constrain useful admission; admitting every queued user simultaneously is pointless. Serve queue/status at an edge-safe tier and maintain bounded checkout/database in-flight work. Report sold, reserved, expired and payment-unknown amounts separately.
Exercise: User U holds two units, payment outcome is unknown, and U retries with a different request key. May it reserve two more?
Show answer and explanation
Answer: No under the cap. held_qty+bought_qty already equals two; another identity is another operation, not resolution of the first. Return the original reservation status when its request key is reused, and reconcile unknown payment without releasing stock twice.
Technical references
How can the service admit users gradually while still guaranteeing that 10,000 units are never reserved twice?
Your design draft
Clarify assumptions, explain your approach, and test the difficult cases. Save your draft, then compare it with the study notes.
Self-review checklist
Self-guided practice. Automated AI feedback and code execution are not connected.