Design the checkout and inventory system for a large e-commerce platform — accurately tracking stock across a massive, frequently-changing catalog while handling flash-sale-level concurrent demand for the same items without overselling.
[Product Page] → [Product Service] (cached, eventually-consistent stock display)
[Checkout] → [Cart Service] → [Inventory Reservation Service] → [Payment System]
│
▼
[Inventory DB] (strongly consistent for the reservation itself)
Two different consistency models coexist deliberately in the same system: the PRODUCT PAGE'S displayed stock count can be slightly stale (cached, eventually consistent — a customer seeing "12 in stock" when it's actually 10 is a minor, acceptable inconsistency), while the actual RESERVATION at checkout time must be strongly consistent (two customers must never both successfully reserve the last unit). Recognizing that different parts of the same system can and should make different consistency tradeoffs is a core System Design Interview Playbook skill (see CAP theorem's discussion in HLD Fundamentals Refresher).
Add to cart / begin checkout → RESERVE 1 unit (decrement available, increment reserved)
with a TTL (e.g. 15 minutes)
Payment succeeds → CONFIRM reservation (reserved → sold, permanent)
Payment fails / cart abandoned / TTL expires → RELEASE reservation (reserved → available again)
This mirrors the authorize/capture separation from Payment — Core Flow directly: a reservation is a temporary hold, not a final commitment, letting the system recover cleanly (release the stock back) if the customer never completes checkout — without this, stock would be either oversold (no reservation at all) or permanently locked up by abandoned carts (immediate, non-expiring decrement).
For extreme, predictable demand spikes on specific items, standard mitigations include: a virtual waiting room / queue in front of checkout (smoothing the request rate rather than letting all 500,000 requests hit the reservation system simultaneously), and pre-partitioning the hot item's stock count across multiple shards/counters (splitting 10,000 units into, say, 10 counters of 1,000 each, reducing contention on any single counter) — directly applying Failure Scenario Walkthroughs' hot-key mitigation to a concrete inventory scenario.
GET /api/v1/products/{id}
→ 200 { "id": "prod_1", "price": 2999, "stockDisplay": "IN_STOCK" } (cached, approximate)
POST /api/v1/cart/{cartId}/reserve
Body: { "productId": "prod_1", "quantity": 1 }
→ 200 { "reservationId": "res_1", "expiresAt": "..." }
→ 409 { "error": "OUT_OF_STOCK" }
POST /api/v1/checkout/{cartId}/complete
→ triggers Payment System charge → on success, confirms all reservations in the cart
inventory
productId, totalStock, reservedStock, soldStock,
availableStock = totalStock - reservedStock - soldStock (computed, or maintained atomically)
reservations
id, cartId, productId, quantity, status (ACTIVE/CONFIRMED/RELEASED), expiresAt
The reservation decrement must be an ATOMIC conditional update (UPDATE inventory SET reservedStock = reservedStock + 1 WHERE productId = ? AND availableStock >= 1), checked and applied as one database operation — a naive read-then-write (check available, then separately update) reintroduces the exact race condition idempotency/atomic-operation discussions elsewhere in this course exist to prevent (see Payment — Idempotency Implementation's check-then-insert race for the same underlying pattern).
Product browsing scales via standard read-heavy patterns: caching (Caching Strategies), read replicas, and a CDN for static product assets. Inventory reservation for NORMAL-demand items scales by sharding inventory records across database shards (by productId). Inventory reservation for HOT items (flash sales) needs the additional counter-splitting/waiting-room mitigations above, since sharding by productId alone still leaves one specific hot product's record as a single contention point.
A single hot product's inventory record during a flash sale is the canonical bottleneck this design has to handle explicitly — every other part of the system (browsing, cart, payment) can scale via standard horizontal patterns, but one row representing one product's stock count cannot be sharded away without the counter-splitting technique described above, since correctness requires all reservation attempts to ultimately agree on the same remaining-stock number.
Payment fails after a reservation was made: the reservation must be explicitly released (not left dangling) — typically triggered both by the payment failure response itself AND, as a safety net, the reservation's own TTL expiry (mirroring the dead-letter/reconciliation safety-net pattern from Payment — Failure Handling & Reconciliation).
The reservation service itself is briefly unavailable during a flash sale: failing closed (reject new checkout attempts with a clear retry message) is correct here, unlike a rate limiter's typical fail-open default — accepting a checkout that can't actually verify/reserve stock risks the exact overselling this design exists to prevent.
Two concurrent requests for the last unit: the atomic conditional update in the database (not application-level locking) is what guarantees only one of the two requests succeeds — this is the same atomicity principle as Payment — Idempotency Implementation's database-level unique constraint, applied to a decrement instead of an insert.