Design Ticketmaster / BookMyShow — System Design
Design a ticket booking system where the core challenge is concurrency: no double-booking under a flash-sale stampede. The reservation pattern, pessimistic vs optimistic locking, strong consistency, and virtual waiting rooms.
🔴 High-Level Design · Super-senior · the concurrency deep dive
"Design Ticketmaster / BookMyShow" — a system to book seats for concerts, movies, and matches. On the surface it's a CRUD app. But it hides one of the hardest problems in system design: when 50,000 people try to grab the same 100 seats in the same second, how do you sell each seat to exactly one person? That's a concurrency problem, and handling it is what this interview is about. Let's design it.
Step 1 — Requirements
- Functional: browse events and available seats; book specific seats; pay; get a confirmation. Seats must never be double-sold.
- Non-functional: strong consistency for seats (correctness is non-negotiable — one seat, one owner), handle extreme spikes (a popular show sells out in seconds), and stay available. This is a read-heavy browse workload with a fiercely write-contended booking moment.
Step 2 — The heart of it: no double-booking
🔴 The whole design revolves around one invariant: a seat is sold to exactly one person. The naive "check if available, then mark booked" has a fatal race condition — two users both read "available" and both book. You need a proper concurrency-control mechanism. There are two classic approaches:
| Approach | How | Best when |
|---|---|---|
| Pessimistic locking | Lock the seat row (SELECT … FOR UPDATE) before booking; others wait/fail |
High contention (popular shows) — no wasted work |
| Optimistic locking | Don't lock; at commit, check a version/status didn't change — retry if it did | Low contention — cheaper, more concurrent |
Either way, the seat update is an ACID transaction with strong consistency — this is exactly the case where you do not use eventual consistency. Naming pessimistic-vs-optimistic locking is the core of a strong answer.
Step 3 — The reservation pattern (locks + a timer)
🔴 Booking isn't instant — the user needs time to pay. So you don't go straight from "available" to "booked"; there's a middle state: reserved.
When you select a seat it becomes reserved for a few minutes (a lock with a TTL). Others see it as unavailable. If you pay in time → booked. If the timer expires or payment fails → it's released back to available. This reservation-with-expiry is the elegant pattern that gives users time to pay without letting a seat be held forever — and it prevents inventory from being locked up by abandoned carts.
Step 4 — Surviving the flash-sale stampede
🔴 When tickets for a huge show drop, millions hit "buy" simultaneously — a thundering herd that can melt the database with lock contention. Two senior mitigations:
- Virtual waiting room / queue: put incoming users in a queue and admit them to the booking page in controlled batches. This smooths the spike into a rate the booking system can actually handle — exactly what real ticketing sites do ("You're number 12,000 in line").
- Cache the read path: browsing events/seat maps is read-heavy, so serve it from a cache/CDN. Only the actual reserve/book action hits the strongly-consistent database. Separating the hot read path from the contended write path is key.
Step 5 — Architecture & data
events: event_id, name, venue, time
seats: seat_id, event_id, status(AVAILABLE|RESERVED|BOOKED),
reserved_until, user_id, version ← version for optimistic lock
bookings: booking_id, user_id, seat_ids, payment_status
Services: a Booking Service (owns the reserve→book transaction with locking), a Payment Service (see payment design — idempotent!), and browse served from cache. A background job releases expired reservations. The seat inventory lives in a relational DB precisely because it needs ACID transactions and strong consistency.
The interview-ready walkthrough
"The whole problem is no double-booking under high concurrency. I use a reservation pattern: selecting a seat moves it AVAILABLE → RESERVED (a lock with a ~10-minute TTL) → BOOKED on payment, releasing it if the timer expires. The reserve step is an ACID transaction with locking — pessimistic (SELECT FOR UPDATE) for hot shows, optimistic for quieter ones — on a strongly-consistent DB. For flash sales I add a virtual waiting-room queue to throttle the stampede and serve the browse path from cache/CDN, so only real booking actions hit the contended DB. Payment is idempotent." That leads with concurrency control — exactly the crux the question is testing."
What to read next
- Design a Payment System — the checkout half of booking
- ACID & Isolation Levels — locking and transactions in depth
- Caching Strategies — separating the hot read path
- ← The complete System Design guide (hub)