Question Breakdowns

Ticket Booking

A worked system design: sell a fixed set of seats for an event without double-booking, under a huge burst of demand. Seat reservation, holds, and the contention problem at its purest.


Ticket booking (think concerts, flights, cinemas) is the purest version of the contention problem: a fixed, scarce inventory of specific seats, and a sudden flood of people all trying to grab the same good ones. Nobody may be sold a seat twice, yet the system must stay fast under a burst that dwarfs steady-state traffic. Get the reservation model right and the rest follows.

1. Requirements

Functional:

  • Browse events and see which seats are available.
  • Reserve specific seats, then pay to confirm.
  • A seat someone is mid-purchasing can't be sold to anyone else.

Non-functional:

  • No double-booking, ever — strong consistency on seat state. This is non-negotiable.
  • Handle bursts — a popular on-sale is a thundering herd at t=0.
  • Fair-ish — a held seat is released if the buyer dawdles, so stock isn't locked up forever.

2. Scale check

Browsing ("show me the seat map") is read-heavy and spikes hard at on-sale. Booking is comparatively rare but must be perfectly correct. Takeaway: cache and scale the read path aggressively, but keep the seat-state write path strongly consistent and simple. Don't let the burst on the read side jeopardise correctness on the write side.

3. Seat states

Every seat is a small state machine:

available → held (by user, with expiry) → booked
             └────── expires ──────► available

The whole design is: move a seat from available to held atomically, give the hold a short TTL, and only booked after payment succeeds.

4. The reservation: winning the race

When a user clicks a seat, you must atomically check "is it still available?" and claim it, so two simultaneous clicks can't both win. Two sound approaches:

  • Conditional DB update: UPDATE seats SET status='held', held_by=?, hold_expiry=? WHERE seat_id=? AND status='available'. If it updates 0 rows, someone beat you — this is optimistic concurrency and relies on the database's atomicity.
  • Distributed lock / atomic op in Redis: SET seat:{id} held NX succeeds only if the key is unset, with an expiry for the TTL.

The database with a conditional update is the most robust answer — the seat's source of truth and its locking are the same system, so there's no way for them to drift.

5. Holds and expiry

A hold must expire, or abandoned carts lock seats forever. Give each hold a TTL (say 10 minutes). Expiry can be:

  • Lazy: when someone tries to claim a held seat, check if the hold has expired and, if so, take it.
  • Active: a background job sweeps expired holds back to available.

Do both — lazy for correctness, active for keeping the seat map accurate for browsers.

6. Payment then confirm

Payment is slow and external, so hold → pay → book is a small multi-step process. The seat sits in held during payment; on success it becomes booked, on failure/timeout the hold expires and the seat frees. Use an idempotency key so a retried payment doesn't double-charge or double-book.

7. Surviving the burst

  • Virtual waiting room / queue: admit users into the on-sale in controlled batches so the booking system sees steady load instead of a cliff. This is the single most important scale technique for a hot on-sale.
  • Cache the seat map read, and accept it's slightly stale for browsing — the authoritative check happens at reservation, where it's strongly consistent.
  • Scale reads independently of the seat-state writes (Scaling Reads).

Deep dives

  • "Best available" booking: for general admission or "give me 3 seats together," let the server pick and claim atomically rather than the client choosing — fewer races.
  • Hot event isolation: partition by event so one blockbuster on-sale doesn't starve every other event's bookings.
  • Overselling on purpose (airlines): some domains deliberately oversell; that's a business rule layered on top, not a correctness bug.

What to emphasise

Make the atomic hold and the expiring TTL the centre of your answer:

"The crux is contention on scarce seats, so a seat is a state machine — available, held with a TTL, booked — and I move available→held with a single atomic conditional update in the database, so two buyers can't both claim one seat. The hold expires so abandoned carts don't lock inventory. Payment happens while the seat is held and only then flips it to booked, with an idempotency key to avoid double-charging. For the on-sale burst I'd put a virtual waiting room in front to admit users in batches, and cache the seat-map read path while keeping the seat-state writes strongly consistent."


Next up: Dating App — matching, geo, and mutual-interest at scale.