Sweaty Imposter

Design Ticketmaster / Event Ticketing

System DesignStrong ConsistencySpiky TrafficSenior3 min readUpdated

Browsing is an ordinary read-heavy site. The interview is decided in the first minutes of a popular on-sale, when millions of fans want the same 50,000 seats and no seat can be sold twice.

1 · Requirements

The Prompt

Sell reserved seats for concerts and sports. Popular on-sales draw millions of fans at once, and no seat can ever be sold twice.

Functional

  • Search and browse events by name, city, and date
  • Show a live seat map for each event
  • Hold chosen seats for a few minutes during checkout
  • Pay and receive tickets

Non-Functional

  • A seat is never sold twice
  • An on-sale with 10M fans stays up and treats fans in arrival order
  • Seat maps load in under a second and are at most a few seconds stale

2 · Estimates

Drill This Phase First

Show the Answer and Estimates

Answer

About 10 minutes

50,000 seats at 2.5 an order is 20,000 orders. At 2,000 admitted fans a minute, that takes 10 minutes, assuming each admitted fan buys.

Browsing
100M event page views a day: about 1,200 a second, served mostly from cache and the CDN
Bookings
About 1M tickets a day outside on-sales: roughly 12 a second, easy for one SQL primary per shard
On-Sale
10M fans for 50,000 seats. At about 2.5 seats an order, that is 20,000 orders
Waiting Room
10M fans checking their place every 10 seconds: 1M requests a second, handled at the edge and in Redis
Admission
Admitting 2,000 fans a minute keeps checkout near 33 orders a second and sells the show in about 10 minutes
Seats
About 50,000 live events with 20,000 seats each: 1B rows at 100 bytes, roughly 100 GB in SQL sharded by event_id

3 · API

GET /events?q=&city=&date= → { events[] } (search index)
GET /events/:id/seats → { sections[], seats[{ id, status, price }] } (cached a few seconds)
POST /events/:id/holds { seat_ids[] } → { hold_id, expires_at }, or 409 if any seat is taken
POST /holds/:id/checkout { payment_token, idempotency_key } → { order_id, tickets[] }
GET /queue/:event_id/position → { position, eta_s, admit_token? }

Data Model

seats (SQL, sharded by event_id):
  event_id, seat_id (PK)
  section, row, number
  price_cents       : int
  status            : enum(available|held|sold)
  hold_id           : uuid?
  hold_expires_at   : timestamp?

orders (SQL, same shard as the event):
  order_id (PK)     : uuid
  user_id           : uuid
  event_id          : uuid
  seat_ids          : list<string>
  status            : enum(pending|paid|refunded)
  idempotency_key   : string, unique

events (SQL, mirrored to the search index):
  event_id, name, venue, city, starts_at, on_sale_at

4 · High-Level Design

A hold is one conditional update on the seat rows. Checkout charges the card with an idempotency key, then marks the seats sold and writes the order in one transaction, and only if the hold is still the caller's.

5 · Deep Dives

Never Sell a Seat Twice

A hold is one conditional update: mark the seats held only where they are available or their hold has expired, then check that every requested row changed. Two fans racing for seat A12 cannot both win, because the database row decides. A Redis lock with a TTL is faster, at the cost of a second source of truth that can drift from the database.

Holds That Expire

Checkout gets 10 minutes. The hold stores hold_expires_at, and every read treats an expired hold as available, so no background sweeper has to run on time for a seat to come back. If a hold expires while payment is in flight and another fan takes the seat, checkout fails and the charge is voided.

The Waiting Room

An on-sale is a stampede on one event's shard. Queue fans at the edge, give each a signed position, and admit them at the rate checkout can serve. Admitted fans carry a short-lived token that the booking service checks, so calling the API directly skips nothing.

6 · Wrap Up

Name the next risks: bots that buy out the house, which call for purchase caps per account; resale and transfers; and mass refunds when a show is canceled.

The Short Version

Hold seats with a conditional update that sets an expiry, then confirm them in a SQL transaction after payment. A virtual waiting room meters hot on-sales, and event search lives in a search index.