Core Challenge and Entities
A flash sale creates a sudden traffic spike (100x-1000x normal load) competing for limited inventory. The core problems: (1) oversell prevention (more orders than items), (2) fairness (all users get equal access), (3) availability (system stays up under load). FlashSale: sale_id, product_id, total_quantity, sold_quantity, price, start_time, end_time, status (UPCOMING, ACTIVE, SOLD_OUT, ENDED). SaleInventory: sale_id, quantity_remaining (Redis counter, authoritative during sale). SaleOrder: order_id, sale_id, user_id, status (RESERVED, CONFIRMED, EXPIRED, CANCELLED), reserved_at, expires_at (reserved_at + 10 minutes), confirmed_at. QueueEntry: entry_id, sale_id, user_id, position, entered_at, token (signed JWT for purchase authorization).
Pre-Sale Queue and Fair Access
class FlashSaleQueueService:
def join_queue(self, sale_id: int, user_id: int) -> dict:
sale = self.db.get_sale(sale_id)
if sale.status != "UPCOMING" and sale.status != "ACTIVE":
raise SaleNotAvailable()
# Deduplicate: one queue entry per user per sale
key = f"queue:{sale_id}:user:{user_id}"
if self.redis.exists(key):
position = self.redis.zscore(f"queue:{sale_id}", user_id)
return {"position": int(position), "already_queued": True}
# Add to sorted set (score = timestamp for FIFO ordering)
position = self.redis.zadd(
f"queue:{sale_id}", {user_id: time.time()}
)
self.redis.setex(key, 86400, "1") # dedup key TTL 24h
queue_size = self.redis.zcard(f"queue:{sale_id}")
return {"position": queue_size, "estimated_wait_seconds": queue_size * 2}
def release_batch(self, sale_id: int, batch_size: int):
# Called every 2 seconds: release next batch from queue
# batch_size = min(remaining_inventory, users_to_let_in)
entries = self.redis.zpopmin(f"queue:{sale_id}", batch_size)
for user_id, _ in entries:
token = self._generate_purchase_token(sale_id, user_id, ttl=300)
self._notify_user(user_id, f"Your turn! Complete purchase within 5 minutes.")
self.redis.setex(f"token:{sale_id}:{user_id}", 300, token)
Atomic Inventory Reservation with Redis
class InventoryReservationService:
def reserve(self, sale_id: int, user_id: int,
purchase_token: str) -> str:
# Validate purchase token (signed JWT with sale_id, user_id, expiry)
if not self._verify_token(purchase_token, sale_id, user_id):
raise InvalidToken()
# Atomic decrement: prevents oversell
remaining = self.redis.decr(f"inventory:{sale_id}")
if remaining str:
expires_at = datetime.utcnow() + timedelta(minutes=10)
order = self.db.insert("sale_orders", {
"sale_id": sale_id,
"user_id": user_id,
"status": "RESERVED",
"reserved_at": datetime.utcnow(),
"expires_at": expires_at
})
return order.order_id
Expiry and Inventory Recovery
Reservations expire after 10 minutes if unpaid. Background job (runs every 30 seconds): SELECT order_id, sale_id FROM sale_orders WHERE status=’RESERVED’ AND expires_at < NOW(). For each expired order: UPDATE status='EXPIRED', then INCR inventory:{sale_id} in Redis (return item to pool), then notify the next queued user. This recycles inventory from abandoned reservations back into the pool. Redis key expiry callbacks (keyspace notifications) can also trigger recovery, but a polling job is simpler and more reliable. Race condition: if the user pays at the exact moment of expiry, use an atomic compare-and-swap: only expire if status is still RESERVED (not CONFIRMED).
Shopify system design interviews cover flash sales and inventory. See design patterns for Shopify interview: flash sale and inventory system design.
Airbnb system design rounds cover reservation and availability systems. See patterns for Airbnb interview: booking and reservation system design.
Stripe system design interviews cover atomic payment reservations. Review patterns for Stripe interview: payment reservation and race condition prevention.
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering