You run a flash sale — iPhone 17, only 5 units. 1 lakh users click Buy at the same time. How do you handle it so only 5 proceed to checkout?
How I frame it in the interview
I'd clarify: 5 units total, sale is a hard cutoff, and "proceed to checkout" means a reserved slot — not 1 lakh orders hitting payment. Goal: exactly 5 reservations, no oversell, fair-enough ordering, and the other 99,995 users get a fast sold out without melting the database.
Core design — reserve before checkout
Never let 100k users open checkout and fight at payment. Split into phases:
- Phase 1 — Admission: Rate-limit and queue at edge (per user / IP). Static product page from CDN; "Buy" hits a lightweight
POST /reserve, not full order creation. - Phase 2 — Atomic decrement: One counter for
iphone17stock. Reserve succeeds only ifavailable > 0in a single atomic operation (RedisDECRwith rollback, or DBUPDATE … WHERE available > 0). - Phase 3 — Reservation token: Winners get a short-lived
reservation_id(e.g. 10 minutes). Only that token unlocks checkout. Everyone else sees sold out immediately. - Phase 4 — Payment: On pay success → confirm sale, decrement permanent inventory. On TTL expiry or abandon → release reservation, increment stock back (or move to waitlist).
CDN / edge cache API gateway + rate limit Inventory service Checkout
(show "sale live") (token bucket per user/IP) (source of truth)
| | | |
100k users -----------------> queue / shed load -------------> reserve(iphone17, qty=1)
| | |
| | atomic: stock 5 -> 4 (Redis DECR / DB)
| | |-- 5 winners: RESERVATION id
| | |-- 99,995: 409 SOLD_OUT (fast fail)
| | |
| | reservation TTL 10 min
| | |--> only reserved users -> checkout URL
| | |--> pay within TTL or stock releasedWhy this works under 1 lakh concurrent clicks
The hot path is O(1) atomic decrement on one key/row — not 100k transactions scanning inventory. Redis (or a sharded counter) handles millions of ops/sec; DB row lock on a single SKU row serializes but still finishes in milliseconds for 100k — losers fail fast with no heavy work.
99,995 users get HTTP 409 Sold Out or 200 { available: 0 } from cache — no cart, no payment session, no order row. That keeps checkout and payment systems clean.
// Atomic reserve — only 5 succeed (Redis example)
// SET iphone17:stock 5 NX (preload before sale)
Long remaining = redis.decr("iphone17:stock"); // atomic per key
if (remaining < 0) {
redis.incr("iphone17:stock"); // roll back overshoot
return soldOut();
}
String reservationId = UUID.randomUUID().toString();
redis.setex("reservation:" + reservationId, 600, userId); // 10 min hold
return ReserveResponse.ok(reservationId, checkoutToken);
// DB alternative: single-row update
// UPDATE inventory SET available = available - 1
// WHERE sku = 'iphone17' AND available > 0
// RETURNING available; -- 0 rows = sold outWalkthrough — second 0 of the sale
Stock preloaded: iphone17:stock = 5. User A–E hit reserve in the first 50ms — five DECR return 4,3,2,1,0. User F's DECR returns -1 → rollback to 0, sold out. Users G–100,000 never touch checkout; UI polls or SSE pushes "0 left". The five holders complete payment within 10 minutes; if user C abandons, TTL fires → stock becomes 1 → optional waitlist or next flash window.
Weak answer to avoid
"We auto-scale Kubernetes to handle 1 lakh requests." Scaling compute does not fix inventory — without atomic reserve you still oversell 500 orders for 5 phones, or your DB dies on 100k concurrent writes to the same row.
Follow-up questions
Redis DECR fails after network partition — risk of oversell?
Redis is fast but not the legal ledger. Pattern: Redis for admission speed, async confirm to DB with unique constraint on (sku, reservation_id). Reconciliation job compares Redis vs DB before shipment. For strict correctness, some teams use DB-only with optimized row update and accept slightly lower peak TPS.
User wins reservation but payment fails — what happens to the unit?
Reservation TTL releases the slot; stock increments (or moves to next waitlisted user via queue). Payment uses idempotency keys so retry does not double-charge. Only PAID state finalizes the one of five units.
How do you stop bots buying all 5?
CAPTCHA at queue entry, device fingerprint, one reservation per verified account, rate limits, and optional lottery queue (random 5 from waitlist) for hype drops. Business trade-off: speed vs fairness.
Product page still shows "Add to cart" from CDN cache after stock is 0?
Do not cache stock count at CDN. Buy button calls live /reserve or /stock API. Show "checking availability…" then sold out or reserved. Short TTL (1–2s) on stock read if you must cache.
Same user clicks Buy in 10 tabs — can they get 2 of 5?
Enforce one active reservation per user_id (Redis SET user:123:reservation NX). Second tab gets "you already have a slot" or shares the same reservation token.