System Design

Design a Flash Sale System

Step 1 / 9

Learn system design by building a flash-sale / limited-inventory system (think a 1000-unit drop with millions of buyers, or Ticketmaster-style scarcity) step by step.

The numbers to beatwaiting roomqueue the herdN/secadmit in batchesedgeshed load early

Deep cut · 11:31

Watch a thousand units get sold to one thousand and forty seven people

This page gives you the architecture. The masterclass withholds it: it opens on a drop that oversells by forty seven while nothing crashes and every service reports success, then derives the whole design from five jobs and six numbers before a single box is allowed on screen. Every part that lands afterwards points back at the requirement that bought it.

  • See the bug, not the word: two shoppers read the same number before either writes it back — the read-then-write gap, shown frame by frame rather than named.
  • Watch each part get bought: the atomic decrement by zero oversells, sharding by a million writes a second, admission by two million in one second, the hold timer by a ninety-second card, the durable queue by a machine dying mid-sale.
  • Take it into the interview: derive the design from the requirements instead of reciting the diagram — including what every fork cost.

In the interview room

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

Functional requirements

What it must do — agree on these before drawing a single box.

  • Never oversell: sell exactly the available units — item 1001 can never be sold — via a single atomic DECR the store serializes.
  • Survive the herd: absorb a 1000× spike with admission control — a virtual waiting room that rate-limits, filters bots and admits users in controlled batches.
  • Reserve, then confirm: a winning decrement holds the unit with a short TTL; pay to turn it into an order, or the hold expires and the unit returns to stock.
  • Process orders async: enqueue each confirmed reservation and run payment and fulfillment off the hot path, with an idempotency key so retries never double-charge.
  • Keep it fair: per-user purchase limits, rate limiting, CAPTCHAs and queue tokens so one bot script can’t sweep the whole drop.

Non-functional requirements

The qualities that shape the whole design — each one names the mechanism that buys it.

Stay up under a 1000× spike
Admission control and a virtual waiting room shed and pace the flood at the edge, admitting N/sec so the core only ever sees a survivable trickle.
No oversell under massive concurrency
A single atomic decrement (Redis DECR / conditional UPDATE) the store serializes — exactly the available buyers get a non-negative result, so item 1001 is never sold.
A win doesn’t block on slow payment
Model the win as a reservation with a short hold TTL, not a final sale, so a slow or failed payment neither blocks the unit forever nor sells it to a non-payer.
The fast path never waits on slow steps
Enqueue confirmed reservations on a durable queue and process payment, persistence and fulfillment asynchronously off the hot path, protected by the hold TTL.
Retries never double-charge
Payment runs with an idempotency key and the confirm step is reversible — a failed payment releases the unit back to stock via an atomic increment.
One blazing-hot key survives millions of writes
Keep the counter in a fast in-memory store, optionally shard the stock into buckets to spread contention, and reconcile against the durable store.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

An atomic counterover a DB transaction with a table lock per purchase

A fast atomic Redis DECR (or conditional UPDATE) lets exactly the available buyers win at in-memory throughput with no read-then-write gap. A table lock serializes every purchase through one lock — correct, but far too slow for the throughput and it builds a huge queue.

Admission control at the edgeover autoscaling the core to serve everyone

A virtual waiting room rate-limits and admits N/sec so the core only sees a survivable trickle. Autoscaling to serve two million in one second for a few thousand units is wildly expensive, often impossible fast enough, and pointless since almost everyone fails to get a unit.

A reservation with a hold TTLover an immediate final sale on decrement

Holding the unit briefly means slow or failed payment neither blocks it forever nor sells it to a non-payer; if the TTL expires the unit returns to stock. Selling before payment lets non-payers permanently consume units, under-selling the drop.

Enqueue and confirm asyncover payment and fulfillment synchronously before confirming

The reservation is instant while slow, fallible work runs off the hot path on a durable queue, protected by the hold TTL. Doing payment and fulfillment synchronously jams the whole flow under load and couples the quick gate to slow, fallible steps.

An in-memory counter, sharded into bucketsover a single disk-based inventory row under locks

A fast in-memory store sustains millions of atomic decrements a second, and sharding 1000 units into ten counters of 100 spreads the contention across keys. A single hot row under millions of writes would crush a normal database.

What this teaches

Learn system design by building a flash-sale / limited-inventory system (think a 1000-unit drop with millions of buyers, or Ticketmaster-style scarcity) step by step. An interactive guide covering why a read-then-decrement oversells, atomic inventory decrement, admission control and a virtual waiting room to survive the thundering herd, reserve-then-confirm with async order processing, fairness and anti-bot, and the unhappy paths (reservation expiry, hot-key, idempotency).

Key takeaways

  • Read-then-decrement oversells under concurrency — thousands read the same stock before any write.
  • Admission control / a virtual waiting room paces the herd so the core sees a survivable trickle.
  • An atomic decrement (Redis DECR / conditional UPDATE) guarantees exactly N buyers win — never item 1001.
  • A win is a reservation with a hold TTL, not a final sale — pay to confirm, or it's released.
  • Enqueue confirmed reservations and do payment/fulfillment async off the hot path.
  • Payment is idempotent (no double-charge) and reversible (failure returns the unit to stock).
  • Tame the single hot inventory key (shard it), enforce fairness/anti-bot, and reconcile to the durable store.

Concepts covered

  • What makes a flash sale hard?
  • Read-then-decrement races
  • Admission control & the waiting room
  • Atomic inventory
  • Reserve, don't sell yet
  • Async order processing
  • Payment and confirmation
  • The hot-key & fairness
  • Expiry, idempotency & under-sell

Design a Flash Sale System — read the full walkthrough as text

the same steps, decisions & trade-offs, for reading, reference & search

The big idea

What makes a flash sale hard?

At 12:00:00 sharp, a 1000-unit drop opens and two million people slam "buy" in the same second. Two things must both hold: you sell exactly 1000 units — never a single one more (overselling a sold-out item is a real, costly failure) — and the site stays up under a spike 1000× its normal load. Scarcity plus a thundering herd.

A flash sale system guarantees no oversell under extreme concurrency while surviving the traffic spike. Two independent problems: correctness (an atomic inventory decrement so item 1001 can never be sold) and scale (admission control + a queue so the herd never crushes the core). Reserve fast, confirm async.

How to read this: Each step opens with a real design decision — make the call before I show you what ships. Watch the design grow, and at the end drop the atomic counter and kill the queue to see correctness vs the herd. Hit Begin.

Step 1 · The oversell bug

Read-then-decrement races

The naive purchase: read stock, if it's > 0 decrement it and give the user a unit. Under millions of concurrent buyers, why does this sell more units than exist?

Design decision: read stock; if > 0, decrement. Why does this oversell under massive concurrency?

The call: Read and decrement aren't atomic — thousands read the same positive stock and all pass, overselling. — With a gap between reading stock and decrementing it, massive concurrency means many buyers observe the same "in stock" value before any of them writes, so they all proceed and the count goes negative. You need the check-and-decrement to be one atomic step.

Read-then-decrement is two steps, so under massive concurrency thousands of buyers all read the same "in stock" value before any decrement lands — they all pass the check and all decrement, driving stock negative. That's overselling. The fix is to make "if available, take one" a single atomic operation that the store serializes.

The race is the enemy: Overselling is a classic lost-update race amplified by extreme concurrency. The only robust fix is atomicity: decrement-if-positive as one indivisible operation, so exactly the available number of buyers can ever succeed. Everything else builds on that guarantee.

Step 2 · Survive the flood

Admission control & the waiting room

Even with correct inventory, two million requests hitting your core services in one second will melt them. You can't scale to serve everyone instantly for a few thousand units. How do you protect the system from the herd itself?

Design decision: Two million requests in one second for 1000 units. How do you protect the core?

The call: Admission control: a gate/virtual waiting room that rate-limits and admits users in controlled batches. — A front gate absorbs the flood: it rate-limits, filters bots, and lets users through in manageable batches (or a "virtual waiting room" that queues everyone and admits N/sec). The core services only ever see a controlled trickle, not the full spike.

Put admission control in front: a gate (often a virtual waiting room) that rate-limits, filters bots, and admits users in controlled batches — e.g. queue everyone and let N per second through to actually attempt a purchase. The core services only ever see a manageable trickle, so a 1000× spike becomes a steady, survivable stream. Most users wait in line; the system stays up.

Shed and pace at the edge: You can't (and shouldn't) serve two million buyers instantly for a thousand units. Admission control turns an unservable instantaneous flood into a paced queue the core can handle, protecting the system and giving users a fair, orderly "you're in line" experience instead of errors.

Step 3 · Never sell 1001

Atomic inventory

Admitted users now attempt to grab a unit. This is the correctness core: how do you let exactly 1000 succeed and everyone after them get "sold out", under heavy concurrency, fast?

Design decision: Admitted buyers grab units. How do you let exactly 1000 succeed, fast, with no oversell?

The call: An atomic decrement on a fast counter (e.g. Redis DECR): a unit is yours only if the result stays ≥ 0. — Keep remaining stock in an atomic in-memory counter and decrement it atomically per purchase; the buyer wins only if the post-decrement value is still ≥ 0, otherwise it's sold out (and you can undo). Atomicity guarantees exactly the available count succeed — no oversell — at very high throughput.

Hold remaining stock in a fast atomic counter (Redis DECR / a Lua script, or a DB UPDATE … SET stock=stock-1 WHERE stock>0). Each purchase does one atomic decrement; the buyer wins only if the result stays ≥ 0, otherwise it's sold out (release the decrement). Atomicity guarantees exactly the available number of buyers can ever succeed — item 1001 is never sold — at in-memory throughput.

Atomic decrement = the gate: The single atomic decrement is the whole correctness story: because the store serializes it, there's no read-then-write gap, so no oversell is possible regardless of concurrency. Redis gives the throughput to run it millions of times a second; the durable store stays the ultimate truth.

Step 4 · You won — briefly

Reserve, don't sell yet

A winning decrement means the user gets to buy — but they still have to pay, which takes seconds and can fail. If you only decrement on successful payment, slow buyers block the unit; if you sell before payment, non-payers steal units. What state is the unit in right after the grab?

Model the win as a reservation, not a final sale. A successful decrement reserves the unit for that user with a short hold TTL (e.g. 5–10 minutes) to complete checkout. The unit is neither freely available nor permanently sold — it's held. If they pay, it becomes a confirmed order; if the hold expires, the reservation is cancelled and the unit is returned to stock (an atomic increment) for someone else.

Reserve → confirm → (or release): Separating "grabbed a unit" from "paid for it" is essential: payment is slow and fallible, so you hold the unit briefly rather than block it forever or sell it to a non-payer. The TTL is the safety valve that reclaims units abandoned at checkout — the same reservation pattern as booking a seat.

Step 5 · Decouple the slow parts

Async order processing

Payment, fraud checks, inventory persistence, notifications and fulfillment are all slow relative to the instant decrement. Doing them synchronously while the user (and thousands of admitted others) wait would jam the whole flow. How do you keep the grab fast?

Design decision: Payment/fulfillment are slow. How do you keep the fast "grab" from being blocked by them?

The call: On a successful reservation, enqueue an order and process payment/fulfillment asynchronously off the hot path. — The reservation is instant; the confirmed grab is put on a durable queue, and workers handle payment, persistence and fulfillment at their own pace while the hold TTL protects the unit. The fast path stays fast; slow, fallible work is isolated and retryable.

Once a unit is reserved, enqueue the order on a durable queue and process the slow work — payment, persistence to the durable store, fraud checks, fulfillment, notifications — asynchronously off the hot path. The user gets an instant "you're in, completing your order" while workers handle it at their own pace, protected by the reservation hold. The fast reservation path never waits on slow, fallible steps.

Fast grab, async confirm: Split the flow into a fast, correctness-critical reservation (atomic decrement) and a slow, decoupled fulfillment (queue + workers). The queue absorbs bursts, makes the slow steps retryable and durable, and keeps the reservation gate responsive. The hold TTL bridges the two — the unit is safe while the queue drains.

Step 6 · Money & completion

Payment and confirmation

The async worker now has to actually charge the winner and turn the reservation into a real order — and payments are slow, can fail, and must never double-charge.

The worker takes the reserved order and runs payment with an idempotency key (so a retry never double-charges), then, on success, confirms the order in the durable store — the reservation becomes a paid, fulfilled order. On payment failure or timeout, cancel the reservation and release the unit back to stock (atomic increment) so it isn't lost. Notify the user of the outcome. Payment is just a reliable, idempotent step in the async pipeline, with the hold TTL as its deadline.

Idempotent, reversible completion: The confirm step must be idempotent (retries are inevitable under load) and reversible (a failed payment returns the unit to inventory, not into a void). Tie it to the reservation TTL so an abandoned checkout automatically frees its unit — no unit is ever stuck "held" forever or sold to a non-payer.

Step 7 · The hot single item

The hot-key & fairness

Everyone is contending on one inventory counter — a single, blazing-hot key that every purchase hits, plus determined bots trying to grab everything and the need for the sale to feel fair.

Tame the hot key: the atomic counter is one cell every buyer touches, so keep it in a fast in-memory store, optionally shard the stock into buckets (1000 = 10×100) so decrements spread across keys, and pre-warm/replicate it. Enforce fairness & anti-abuse: per-user purchase limits, rate limiting and bot filtering at admission, CAPTCHAs, and pre-issued queue tokens so one script can't sweep the drop. Persist the fast counter's state to the durable store and reconcile, so the in-memory gate and the system of record never disagree — and the durable store is the final backstop against oversell.

One key, many buyers: A flash sale is the ultimate hot-key problem: a single number under millions of writes. In-memory atomics handle the throughput; sharding the counter spreads the contention; admission + per-user limits + bot defense keep it fair. The durable store reconciles and guarantees the books are right.

Step 8 · The sharp edges

Expiry, idempotency & under-sell

Real drops bring edge cases: units held by users who never pay, duplicate requests from retries and double-clicks, and the opposite risk of under-selling (units stuck in expired holds while the item shows "sold out").

Reclaim abandoned units by expiring reservations and returning them to stock, and re-open the sale if reclaimed units appear — so you don't under-sell (show sold-out while units are stuck in dead holds). Make every step idempotent (idempotency keys, dedupe) so retries, double-clicks and at-least-once queue delivery don't double-reserve or double-charge. Give clear real-time status (in line / you won / sold out) to cut refresh-hammering. And treat the durable store as the final arbiter of inventory, reconciling the fast counter against it so neither oversell nor lost units can persist.

Design for the unhappy path: Abandoned hold → expire + return to stock (avoid under-sell). Retry/double-click → idempotency. Herd refreshing → live status. Counter vs truth → reconcile to the durable store. The atomic-decrement core is correct; these guards make the whole sale correct, fair, and neither over- nor under-sold.

You did it

You just designed a flash sale system.

  • Read-then-decrement oversells under concurrency — thousands read the same stock before any write.
  • Admission control / a virtual waiting room paces the herd so the core sees a survivable trickle.
  • An atomic decrement (Redis DECR / conditional UPDATE) guarantees exactly N buyers win — never item 1001.
  • A win is a reservation with a hold TTL, not a final sale — pay to confirm, or it's released.
  • Enqueue confirmed reservations and do payment/fulfillment async off the hot path.
  • Payment is idempotent (no double-charge) and reversible (failure returns the unit to stock).
  • Tame the single hot inventory key (shard it), enforce fairness/anti-bot, and reconcile to the durable store.
built to sell exactly 1000 units to a million people without overselling one — make the calls, drop the counter, run the gauntlet.
Finished this one? 0 / 65 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs