Design Ticketmaster
Sell 50,000 seats in a minute without selling one twice; hold, pay, release.
- Distributed locks and leases — Mutual exclusion across machines, and why a lease needs a clock.
- Distributed transactions (2PC and sagas) — All-or-nothing across services, or a sequence of steps with compensations.
- Message queues and pub/sub — Decoupling producers from consumers with a durable log in between.
- Caching strategies — Read-through, write-through, write-back, TTLs and invalidation.
- Idempotency and exactly-once — Making a repeated request harmless, which is how 'exactly once' is really built.
01The design, in the six-step framework
1. Requirements
Functional
- Browse events and see a seat map with availability.
- Select seats, hold them for 10 minutes while paying, then confirm or release.
- Never sell the same seat twice; never charge without a seat.
- Handle a sale that opens at a fixed time to a crowd far larger than the venue.
- Out of scope: resale, dynamic pricing, seating-chart design tools.
Non-functional
- A popular on-sale: 1 million people arrive within a minute for 50,000 seats.
- Seat map loads in under 500 ms; a hold is confirmed or refused in under 1 s.
- Strong consistency on seat state; browsing availability may be seconds stale.
- 99.99 percent availability of the purchase path during an on-sale.
- Fairness: order of arrival is respected within a few seconds; bots are limited.
1 M arrivals ÷ 60 s ≈ 17,000 arrivals per second, against a venue of 50,000 seats, so 95 percent of arrivals will not get a seat no matter how fast the system is.
Seat state for one event is 50,000 rows of 100 B = 5 MB; even 10,000 events on sale is 50 GB, small enough for a single transactional store per region plus caches.
Holds: if every arrival could hold 4 seats, holds would exceed supply within 3 s; the waiting room is what makes the hold rate match the supply.
Seat map reads: 1 M people refreshing every 5 s is 200,000 reads a second, served from a cache that is updated on every state change.
2. Core entities
- Event: event_id, venue, start time, on-sale time, state.
- Seat: event_id, seat_id, section, row, price tier, state (available, held, sold), hold_id, version.
- Hold: hold_id, event_id, user_id, seat list, expires_at, state.
- Order: order_id, hold_id, user_id, amount, payment reference, state.
- Queue token: user_id, event_id, position, admitted_at.
3. API
GET /events/{id}/seats: the seat map with availability from the cache. Called by the client.POST /events/{id}/queue: join the waiting room; returns a token and a position; the client pollsGET /queue/{token}.POST /events/{id}/holdswith seat ids and the admitted token; returns hold_id and expiry, or the seats that were unavailable.POST /holds/{id}/checkoutwith a payment method and an idempotency key; returns the order.DELETE /holds/{id}: release early.
4. High-level design
Buying two seats at a hot on-sale:
- Before the on-sale time the user joins the waiting room, which assigns a random position among everyone present at the start and then first-come order after; the client polls its position.
- The waiting room admits users at a rate matched to the hold capacity, say 500 a second, handing each an admitted token valid for 10 minutes.
- The admitted user loads the seat map from the cache and picks two seats; the hold request carries the token.
- The seat service runs one transaction: for each seat, update where state = available and version = expected; if any seat fails, the whole hold fails and the response lists the taken seats. On success it creates the hold with a 10-minute expiry, updates the cache, and schedules an expiry timer.
- Checkout charges the payment provider with the hold id as the idempotency key; on success the order service marks the seats sold and the hold consumed in one transaction.
- If the timer fires on an unconsumed hold, the expiry worker releases the seats and the cache shows them available again.
5. Deep dives
How is a seat never sold twice when 17,000 people a second are trying?
| Option | Behaviour | Trade-off |
|---|---|---|
| Check then write | Read available, then write held | Two requests interleave; both succeed; double sale |
| Pessimistic row lock | Lock the seat rows for the duration of the hold request | Correct; lock waits pile up under contention on popular seats |
| Optimistic conditional update | Update seat set state = held where state = available and version = v; zero rows updated means someone won | Correct, no waiting, the loser gets an immediate answer; needs all seats of a hold in one transaction |
Use the conditional update inside a single database transaction per hold; it is the simplest correct form of a lock because the database already serialises the rows. The seat table is partitioned by event so one hot event lives on one primary, which can do tens of thousands of such transactions a second, far more than the admission rate lets through.
Why a waiting room, and how does it stay fair?
Without admission control, a million clients hammer the hold endpoint; most fail, retry and fail again, and the database spends its capacity on losers. The waiting room turns an unbounded burst into a steady stream sized to the system. Fairness: everyone who arrived before the on-sale moment is shuffled randomly (arrival time in that minute is mostly network luck), later arrivals are queued in order, and one token per verified account limits multiple tabs. Bots are limited at the room's door with device attestation and rate limits per account and network, which is cheaper than fighting them at the seat service.
How do holds expire exactly, and what if the worker dies?
The expiry is stored on the hold row, so the truth is in the database; the timer queue is only a prompt. The worker, on firing, runs release where state = held and expires_at < now, which is safe to run twice. If the worker is down, a sweeper every 30 s scans for overdue holds, so the worst case is a seat returning a little late, never a seat released early. Checkout after expiry fails because the confirm transaction requires the hold to be live; the user is told the seats were released.
How is the seat map fast and almost right?
Reading 50,000 seat rows per map load at 200,000 loads a second is impossible on the primary. The seat service writes every state change to a per-event cache entry (a compact bitmap of states by seat index, about 12 KB per event), and the API serves the map from it; it can be a second stale, which is acceptable because the hold request is the authority. During an on-sale the bitmap is also pushed to CDN edges every second so that even the browsing load leaves the origin.
What does the payment step need from the design?
The provider call can time out after charging; retrying with the same idempotency key returns the first result instead of a second charge. The confirm transaction is written only after a successful charge; a charge whose confirm failed (database down for a moment) is picked up by a reconciliation job that compares provider captures against orders and completes or refunds them. Money and seats are thus linked by the hold id through both systems.
6. What a strong answer sounds like
- Name the two problems separately: correctness on seat state (a transaction with conditional updates) and survival of the on-sale burst (a waiting room); many candidates solve only one.
- Quote 17,000 arrivals a second against 50,000 seats and conclude that admission control is a product feature, not just a technical one.
- Explain why the database row is the truth, the cache is a hint, and the timer is a prompt.
- Expect follow-ups on multi-seat holds (all or nothing), on general-admission tickets (a counter, not rows), and on what the user sees when they lose a race.
- A common mistake is to use a distributed cache lock as the only guard; cache locks expire and are not transactional with the seat row.
02Where this question was reported
- IGotAnOffer · SWE: 'ticketing platform', 'online booking system for a restaurant'; EM: 'ticket booking system'
- IGotAnOffer · 'How would you design a ticketing platform?'
- IGotAnOffer · Low-level: 'train booking system'
- interviewkickstart.com · 'online ticket booking system'
- interviewkickstart.com · Listed
- Hello Interview · Commonly asked Google L5
- Hello Interview · Commonly asked Google L6
- Hello Interview · Commonly asked Google EM
- PracHub · PM: event ticketing with seat holds and high-demand on-sales
- Blind · Lock tickets for a user, release after timeout, queue of users for massive events, compute best number of active users, prevent scammers
- Glassdoor · Round 1: system design, high-level problem space, a booking system (Bengaluru; search snippet)
- interviewkickstart.com · 'Online ticket booking system'.