SysDesignPrep.com
Study guide 156 of 183

Inventory, reservations and flash sales

Never overselling: atomic decrements, reservations with expiry, holds versus commits, sharded inventory counters, queues and virtual waiting rooms for flash sales, bots, and keeping cached stock counts honest.

Reading is half of it. See this used in a real interview: walk through Design Ticketmaster →

Limited stock appears everywhere: seats at a concert, rooms on a night, sneakers in a drop, the last item in a warehouse. The requirement is always the same (never sell more than you have) and the hard case is always the same: a crowd arriving at the same instant for the same few items. This guide covers the patterns for both the everyday case and the flash sale.

The race

Two buyers see "1 left", both click buy, both reads return 1, both write 0. Any design that reads stock, decides in application code, then writes will oversell under concurrency. The check and the decrement must be one atomic operation.

Atomic decrements

For countable stock (100 identical items), let the database do the check and the decrement together:

UPDATE inventory SET available = available − 1
WHERE sku = 'A1' AND available >= 1

If zero rows changed, it is sold out. In Redis, a Lua script or DECR with a check does the same in memory at very high rates. For distinct items (seat 14C, room for one night), use a row per item and a conditional update or a unique constraint. See transactions, isolation levels and locking.

Reserve, then commit

Buying takes time: the user enters details, payment is authorised. Do not decrement only at the end (someone else may take the item while they type), and do not decrement permanently at the start (abandoned carts would lock stock forever). Use a reservation with an expiry:

  1. On "add to cart" or "select seat": move one unit from available to held, with an expiry (5 to 15 minutes) and a reservation id.
  2. On successful payment: convert the hold to sold.
  3. On expiry or abandonment: return the unit to available automatically.

The expiry is enforced by data (an expires_at checked when stock is counted) and by a sweeper that releases expired holds, so a crash never leaves stock stuck. Payment is authorised while the hold exists and captured on commit. See money in system design and Design Ticketmaster.

Hot items: when one row is the bottleneck

A popular SKU means every buyer updates the same row; a database row handles a few hundred to a few thousand contended updates a second. Options:

  • Shard the counter: split 10,000 units into 20 sub-counters of 500; each buyer tries a random sub-counter and moves on if it is empty. Sold-out is when all are empty.
  • Hold stock in Redis for the sale, with atomic scripts, and write sold units to the database asynchronously (with reconciliation, because Redis can lose acknowledged writes on failover).
  • Pre-allocate: hand out tokens (one per unit) from a queue; whoever gets a token gets the item.

Flash sales: control the crowd first

When a million people arrive for 10,000 items, the inventory logic is not the main problem; the crowd is.

  • Virtual waiting room: put arrivals in a queue at the edge, admit them at a rate the backend can handle, and show their position. Randomising order among people who arrived before the start is fairer than rewarding the fastest bot.
  • Pre-scale for the known start time; autoscaling reacts too slowly. See autoscaling.
  • Serve static content from the CDN, so only purchase attempts reach the origin.
  • Rate limit per user, device and payment method.
  • Stop early: once stock is gone, answer "sold out" at the edge instead of letting requests reach the database.

Bots and fairness

Drops attract bots that buy faster than humans and resell. Defences: queue-based admission with randomisation, purchase limits per account and per payment card, device and behaviour signals, verified-fan pre-registration, and making the reward for speed small (a lottery for the right to buy). See rate limiting and resilience.

Showing stock without lying

Product pages show "in stock" or "3 left" millions of times; that must not read the hot inventory row. Cache availability with a short TTL and treat it as approximate; the purchase step is the authority. It is fine to show "in stock" and then fail at checkout occasionally; it is not fine to oversell. Flip pages to "sold out" quickly through cache invalidation when stock reaches zero. See caching.

Overselling on purpose

Airlines and hotels oversell deliberately because some customers do not show up. If the business wants it, make it an explicit, configured allowance with a policy for the overflow, never an accident of concurrency.

Reconciliation

Count sold, held and available units regularly and compare with orders and payments. Holds that outlived their expiry, sold units without paid orders, and paid orders without stock all indicate bugs. See Design Airbnb for the per-night version of the same checks.

Checklist

  • Atomic check-and-decrement or a unique constraint; never read-then-write.
  • Holds with expiry, converted on payment, released automatically.
  • Sharded counters or in-memory stock for hot items.
  • Waiting room, pre-scaling and edge rate limits for flash sales.
  • Bot and fairness controls.
  • Cached, approximate stock for display; the purchase step decides.
  • Reconciliation of holds, sales and payments.

Open in your browser to sign in

Google does not allow sign-in inside this app's built-in browser. Open this page in Safari and sign in there. The link opens this same page.

Tap the ⋯ or share button at the top or bottom of the screen, then Open in browser. Or copy the link and paste it into Safari.