Design Ticketmaster / BookMyShow
Ticketing is a study in contrasts: browsing events is a massive, cache-friendly read workload, while booking a specific seat demands strict correctness — the same seat must never be sold twice. The heart of the design is a short-lived seat reservation (a hold with a TTL) backed by a strongly consistent store, plus a virtual waiting room to survive flash sales like a Taylor Swift on-sale.
Design it yourself
Don't just read it — drag components onto a canvas and get Aria's interviewer review.
Requirements
Functional
- Browse and search events, venues, and showtimes
- View a live seat map with available / held / sold seats
- Reserve (hold) one or more seats for a limited time
- Pay and confirm the booking; release the hold if payment fails or times out
- Prevent double-booking — a seat is sold to at most one user
Non-Functional
- Strong consistency for seat inventory (correctness over availability)
- Very read-heavy browsing (100:1+) but write-critical booking
- Survive flash-sale spikes — 100k+ users hitting one event at t=0
- Reservation timeout ~10 minutes, then the seat returns to the pool
- Payments must be idempotent — never double-charge on a retry
Capacity Estimation
| Browse reads | ~50k RPS at peak (cacheable) |
| Flash-sale users | 100k+ concurrent on one on-sale |
| Seats per event | 1k – 100k |
| Hold TTL | 10 minutes |
| Booking writes | low volume, high correctness |
High-Level Components
API Gateway / Load Balancer
TLS, auth, rate limiting, and routing. During an on-sale it also admits users from the virtual waiting room in controlled batches.
Search Service (Elasticsearch)
Full-text and faceted search over events, artists, venues, and dates. Sourced asynchronously from the events database so search load never touches the transactional path.
Inventory / Booking Service (relational DB)
The source of truth for seats. Uses a strongly consistent relational database (PostgreSQL) with row-level locking (SELECT ... FOR UPDATE) or optimistic version checks so a seat can transition available → held → sold exactly once.
Reservation Store (Redis)
Holds short-lived seat reservations as keys with a 10-minute TTL. A hold is an atomic Redis operation; when the TTL expires the seat is automatically freed without any cron job.
Payment Service (idempotent)
Integrates the payment gateway behind an idempotency key so a retried request never double-charges. On success it commits the booking (held → sold) in one transaction; on failure/timeout the hold is released.
Virtual Waiting Room + Queue (Kafka)
During flash sales, users enter a queue and are admitted in batches. This shields the inventory database from a 100k-user thundering herd and gives users a fair, predictable position.
Architecture Diagram
Deep Dives
The Seat Reservation Lifecycle
A seat moves through three states: available → held → sold. The trick is the temporary "held" state, so a user has time to enter payment details without the seat being sold from under them.
Hold: an atomic operation reserves the seat for this user with a 10-minute TTL. If two users race for the same seat, exactly one wins.
Confirm: on successful payment, the seat is committed to "sold" inside a database transaction.
Expire: if the user abandons or payment fails, the TTL lapses and the seat returns to "available" automatically — no background sweeper needed if you lean on Redis key expiry.
Redis — atomic seat hold with automatic expiry
// Atomic hold in Redis — SET NX (only if not held) with a 10-min TTL
Boolean won = redis.set(
"hold:event42:seatK12",
userId,
SetParams.setParams().nx().ex(600) // NX = only if absent, EX = 600s TTL
);
if (!won) throw new SeatUnavailableException("K12 was just taken");
// user now has 10 minutes to pay; on expiry the key vanishes and the seat freesPreventing Double-Booking (Strong Consistency)
The Redis hold gives speed, but the database is the source of truth for the final sale. Committing a booking must be atomic and serializable.
Pessimistic (SELECT ... FOR UPDATE): lock the seat row, check it’s still available, mark it sold, commit. Simple and correct; the lock is held only for the brief commit.
Optimistic (version column): read the seat with its version, then `UPDATE ... WHERE id = ? AND version = ?`. If zero rows update, someone else won — retry or fail. Better under low contention.
Either way, the database’s ACID guarantees — not application code — are what make double-booking impossible.
SQL — serializable seat commit with row locking
-- Pessimistic commit inside one transaction BEGIN; SELECT status FROM seats WHERE id = 'event42:K12' FOR UPDATE; -- lock the row -- app checks status = 'held' by this user UPDATE seats SET status = 'sold', booking_id = :bid WHERE id = 'event42:K12' AND status = 'held'; COMMIT;
Surviving the Flash Sale (Virtual Waiting Room)
When 100k people hit "buy" the instant tickets drop, letting them all reach the inventory database at once melts it. The industry answer is a virtual waiting room.
Users first enter a queue (a Kafka topic / ordered set) and see "you are number 12,431 in line". The system admits them into the actual booking flow in controlled batches sized to what the inventory DB can safely handle. This converts an unbounded thundering herd into a steady, survivable stream, and gives users a fair, transparent position rather than a coin-flip.
Idempotent Payments
Networks retry. A user double-clicks. A gateway times out and the client resends. Without protection, the customer is charged twice.
Idempotency key: the client generates a unique key per booking attempt and sends it with the payment request. The Payment Service records processed keys; a repeat with the same key returns the *original* result instead of charging again. Combined with the seat transition happening in the same transaction as recording the payment, retries become safe and the "held → sold" step happens exactly once.
Key Trade-offs
Consistency model for seat inventory
Selling the same seat twice is unacceptable. Booking chooses consistency over availability; browsing (a separate path) can stay highly available and cached.
Inventory store: SQL vs. NoSQL
Seat sales need ACID transactions and serializable commits. This is exactly what relational databases are built for; eventual-consistency stores would risk oversell.
How to hold a seat during checkout
A hold with automatic expiry frees abandoned seats with no background job, and an atomic SET NX resolves races between users cleanly.
Handling on-sale spikes
Admitting users in controlled batches protects the transactional database from a 100k-user thundering herd and gives fair, visible queue positions.
Interview Tips
- 1Split the problem immediately: browsing is a cacheable read workload; booking is a strict-correctness write workload. They get different architectures.
- 2The held state (reservation with TTL) is the key idea — describe available → held → sold explicitly.
- 3Name your double-booking defense: SELECT ... FOR UPDATE or an optimistic version check in the DB, not app-level checks.
- 4Bring up the virtual waiting room for flash sales before the interviewer asks "what about Taylor Swift on-sale?".
- 5Payments must be idempotent — mention the idempotency key and why retries would otherwise double-charge.
- 6Lean on Redis TTL to auto-release abandoned holds instead of a cron sweeper.
Discussion
Discussion
Sign in to join the discussion.