Learning pathsA
Question Breakdowns

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

Contract / pseudocode
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

Contract / pseudocode
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

Three architecture decisions for Flash Sale, including the pressure each introduces.
Scroll to inspect the diagram, or open it at full size.

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

Connected responsibilities for Flash Sale. Trace the authoritative and derived paths separately.
Scroll to inspect the diagram, or open it at full size.

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

Flash Sale: baseline request paths.
Scroll to inspect the diagram, or open it at full size.
  1. Buyer → admission gate → stock reservation transaction.
  2. Reservation → order and outbox → payment workflow.
  3. Payment outcome → confirm or release reservation.

Evolve the design under load

Flash Sale: additional scaling and recovery paths.
Scroll to inspect the diagram, or open it at full size.
  1. Edge protection → signed queue tickets → bounded origin.
  2. Inventory token partitions → controlled allocation.
  3. 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

LayerPurposeInvariant
Waiting roomShape arrival rateDoes not own stock
Inventory ownerAtomic reservationTotal reserved ≤ available
Payment workflowResolve external resultLate success is reconciled
Sale status viewFast communicationMay 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.

sql
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.

Conservation for initial stock 10. Initial / 10 / 0 / 0 / 10; Reserve two units / 8 / 2 / 0 / 10; Confirm same hold / 8 / 0 / 2 / 10; Duplicate confirmation / 8 / 0 / 2 / 10; Another hold expires for two / Release only once from its active state / Still 10 across categories
Scroll to inspect the diagram, or open it at full size.

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.

Expired hold and late payment preserve new ownership. Buyer A to Stock authority: Reserve H1 for two units; Buyer A to Payment provider: Payment H1 accepted but response lost; Stock authority to Stock authority: H1 expires; conditional release once; Buyer B to Stock authority: Reserve H2 using released stock; Payment provider to Stock authority: Late success for H1; Stock authority to Buyer A: Compensate H1; never steal H2 inventory
Scroll to inspect the diagram, or open it at full size.

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

Provider idempotency contract example.

PostgreSQL transaction isolation and concurrent updates.

Your study notes