Everyone Adds to Cart, Few Check Out
A flash sale is the same contended inventory as ticketing, but the winning design is the opposite: oversubscribe the cart and decrement only at checkout. A runnable lab measuring the funnel, a payment-TTL stock reclaim, and a per-user anti-scalper cap.
A flash sale is the same contended, finite inventory as selling tickets — but the correct design is the opposite of a seat map. A ticket seat is reserved the moment it's in your cart; a flash-sale item is not, and that's on purpose. You've felt it: at 10:00 sharp everyone can add the item to their cart, but most people hit "sold out" at checkout. That's not a bug — it's the right architecture, and here's why, measured.
Runnable companion:
ecommerce-flash-saleon GitHub.make funnel && make timeout && make limitreproduces every number below (pure Python, no deps). Sibling:high-demand-ticketing.
Decrement at checkout, not in the cart
The key decision is when stock leaves inventory. Reserve-at-cart (ticketing style) decrements on add-to-cart; deduct-at-checkout (flash-sale style) touches nothing until the order is placed. With 400 shoppers, 100 units, as cart abandonment rises:
abandon strategy add-to-cart sold wasted
20% reserve-at-cart 25% 80 20
deduct-at-checkout 100% 100 0
40% reserve-at-cart 25% 60 40
deduct-at-checkout 100% 100 0
60% reserve-at-cart 25% 40 60
deduct-at-checkout 100% 100 0
reserve-at-cart locks a unit for every abandoner, so the cart "sells out" to holds — only 25% of shoppers even get an item into the cart, and at 40% abandonment it sells just 60 of 100 units. deduct-at-checkout keeps 100% of stock available to buyers who actually convert, so it sells the full 100 at every abandonment rate. "Everyone adds to cart, few check out" is the design working: it refuses to waste stock on carts that will be abandoned.
Pending orders need a payment TTL
Deducting at checkout leaves a smaller leak: a buyer places the order (stock −1, pending) then never pays. A TTL cancels the unpaid order and returns the unit to waiting demand:
non-payment no timeout: sold with timeout: sold
10% 90 (90%) 100 (100%)
30% 70 (70%) 100 (100%)
50% 50 (50%) 100 (100%)
At 30% non-payment you sell only 70% of stock without a TTL; the timeout brings it back to ~100%. It's the seat-hold + reaper pattern from ticketing, moved to the order level.
A per-user cap stops scalpers
Flash sales attract bots — a few accounts firing many requests each. 100 units, 200 genuine buyers (one request) + 20 scalpers (50 requests each):
policy to scalpers unique buyers most by one
no limit 83 (83%) 36 8.1
per-user limit = 1 20 (20%) 100 1.0
With no cap, stock goes to whoever sends the most requests — scalpers scoop 83% and only ~36 people are served. A one-per-account cap drops that to 20% and spreads the drop across all 100 unique buyers. It's the inventory-side half of anti-bot defense (pair with auth, rate limits, bot filtering).
What carries over from ticketing
The checkout itself still needs the mechanics proven in the ticketing lab, unchanged: an atomic decrement so a burst of orders never oversells (buying N units is one all-or-nothing update), idempotent payment on a client key, and a virtual waiting room for backpressure when the spike is large. The one real difference is risk appetite: ticketing must never oversell; e-commerce often tolerates a small deliberate oversell (backorder / refund) to lift conversion — a product decision, not a correctness one.
What I'd say in an interview
- Reserve stock in the cart only for unique, rarely-abandoned units (a seat). For fungible goods with high abandonment, oversubscribe the cart and decrement at checkout — you sell the full stock instead of wasting it on abandoners (measured: 60 → 100 of 100 units at 40% abandonment).
- Put a TTL on the pending order so non-payers don't strand inventory — the reaper pattern at the order level (70% → ~100% utilization).
- Cap purchases per user to blunt scalpers (83% → 20% of stock), paired with auth and bot filtering.
- The checkout decrement is still atomic and payment idempotent — same invariants as ticketing; only the reservation point and the oversell tolerance change.