Skip to content
System design course
Ch.4 · Designing real systems·how to build it ·7 min read

Building Ticketmaster

Implement the virtual waiting room with token-based admission, atomic seat holds with TTL, and the confirm-or-release flow.


The virtual waiting room

Users join a queue served by a lightweight, independently-scaled service; admission is rate- controlled so the booking core sees only a trickle:

def enter_queue(user, event_id):
    pos = queue.push(event_id, user)                  # FIFO (or lottery) — Redis list/sorted set
    return {"position": pos, "token": None}

def admission_loop(event_id):                         # admit at a sustainable rate
    while sale_open(event_id):
        capacity = booking_capacity(event_id)          # how many the core can take now
        for user in queue.pop_n(event_id, capacity):
            token = issue_entry_token(user, event_id, ttl="10m")  # signed admission token
            notify(user, {"admitted": True, "token": token})
        wait(throttle_interval)

The queue (position lookups, admission) runs on a cacheable store; the seat service never sees the herd — only token-bearing admitted users.

Gating the seat flow with the token

Every seat action requires a valid admission token, so non-admitted traffic can’t touch the core:

def select_seats(token, seat_ids):
    user = verify_token(token)                        # reject if not admitted / expired
    return hold_seats(user, seat_ids)

Atomic seat holds (no double-sell)

Selecting a seat atomically flips it available → held with a TTL — only one user can win each seat:

def hold_seats(user, seat_ids):
    held = []
    try:
        for seat in seat_ids:
            ok = db.execute(                          # atomic conditional transition
                "UPDATE seats SET status='held', held_by=:u, hold_expires=:exp "
                "WHERE seat_id=:s AND status='available'",
                u=user, s=seat, exp=now()+600)
            if not ok: raise SeatTaken(seat)          # someone else holds it
            held.append(seat)
        return held
    except SeatTaken:
        release(held)                                 # all-or-nothing for the selection
        raise

The WHERE status='available' makes the check-and-hold one atomic step — concurrent buyers of the same seat can’t both succeed. A unique/exclusion guarantee per seat is the safety net.

Hold expiry (don’t strand seats)

A background job (job scheduler) releases expired holds so abandoned carts free up seats:

def release_expired_holds():
    for seat in seats.where(status="held", hold_expires_lt=now()):
        db.execute("UPDATE seats SET status='available', held_by=NULL "
                   "WHERE seat_id=:s AND status='held'", s=seat.id)

Confirm or release

def checkout(token, seat_ids, payment):
    user = verify_token(token)
    charge(payment, price(seat_ids), idempotency_key=token+str(seat_ids))   # idempotent
    with txn():
        for seat in seat_ids:
            ok = db.execute("UPDATE seats SET status='booked' "
                            "WHERE seat_id=:s AND status='held' AND held_by=:u",
                            s=seat, u=user)
            if not ok: raise HoldLost(seat)           # hold expired before pay → refund
        issue_tickets(user, seat_ids)

If the hold lapsed before payment, the confirm fails and the charge is refunded — never sell a seat whose hold expired.

Fairness and anti-bot

  • FIFO/lottery queue order; per-user ticket limits enforced at hold time.
  • Anti-bot — CAPTCHA + rate limits + device/identity checks before queue entry; signed tokens prevent skipping the line.

Scale and failure handling

  • Spike → absorbed by the queue; admission throttled to backend capacity (the core never overloads).
  • Concurrent seat grabs → atomic conditional update; exactly one winner.
  • Abandoned holds → TTL + sweeper free them.
  • Payment fails / hold expired → confirm fails, refund, seat returns to available.
  • Queue service failure → it’s decoupled and replicated; the seat core stays protected.
  • Hot event → seat data sharded by event/section; the section being fought over is the contention point — atomic transitions serialize it correctly.

The takeaway

Concrete signals: a virtual waiting room with token-gated, rate-controlled admission (the herd never reaches the core), atomic seat holds with TTL (no double-sell under extreme concurrency), confirm-or-release with idempotent payment, and fairness + anti-bot. Admission control + atomic holds is the blueprint for any extreme-contention limited-inventory sale.