System Design Interview: Design a Ticket Booking System (Ticketmaster)

System Design: Design a Ticket Booking System (Ticketmaster/Fandango)

A ticket booking system is a classic system design problem involving seat inventory management, race conditions, payment processing, and fairness under extremely high traffic spikes (viral event on-sale moments).

Requirements

Functional: browse events, search by location/date/artist, view seat map, reserve seats (hold temporarily), complete purchase, receive tickets, cancel/refund.

Non-functional: strong consistency for seat allocation (no double-booking), low latency for search, handle spikes (Taylor Swift on-sale: millions of requests in seconds), support 10M+ events, 1B+ tickets sold.

Core Challenge: Preventing Double-Booking

Two users cannot purchase the same seat. This requires careful concurrency control.

Option 1: Optimistic Locking

Each seat row has a version number. Read seat + version, attempt update with WHERE version=old_version. If another transaction committed first, version mismatch → retry.

-- Reserve seat atomically
UPDATE seats
SET status = 'held', held_by = :user_id, held_until = NOW() + INTERVAL '10 minutes', version = version + 1
WHERE seat_id = :seat_id AND status = 'available' AND version = :expected_version;
-- If 0 rows updated: seat taken, show error

Option 2: Redis Distributed Lock

Use Redis SET NX EX to acquire a lock per seat. Only the process holding the lock can reserve the seat. Release after committing to DB.

lock_key = f"seat_lock:{seat_id}"
acquired = redis.set(lock_key, user_id, nx=True, ex=30)  # 30s timeout
if not acquired:
    return "Seat already being reserved"
try:
    # reserve in DB
    db.execute("UPDATE seats SET status='held'... WHERE seat_id=?", seat_id)
finally:
    redis.delete(lock_key)

Seat Hold Flow

User clicks seat
        │
        ▼
   Hold Request
        │
  ┌─────▼────────────────────────────────────┐
  │ Redis: SET seat:{id}:held {user_id} EX 600│  (10-min hold)
  └─────┬────────────────────────────────────┘
        │ success
        ▼
  Checkout timer starts (10 min countdown)
        │
  User completes payment
        │
        ▼
  Payment Service (Stripe/Braintree)
        │ success
        ▼
  Mark seat SOLD in PostgreSQL
  Send confirmation + e-ticket (email/push)
        │
  If timer expires without payment:
        ▼
  Release hold → seat becomes available again

Handling Traffic Spikes (On-Sale Moments)

When a major event goes on sale, millions of users hit the system simultaneously. Solutions:

  • Virtual waiting room: queue users on entry, issue time-stamped tokens, let them in N at a time. Fair, prevents server overload. Ticketmaster, Amazon use this.
  • Queue-based inventory: pre-allocate seat groups to queues; each queue worker processes one user at a time for its group
  • Read replicas: all seat browsing hits read replicas; only reservation writes hit primary DB
  • Cache availability map: cache section-level availability in Redis; only query DB for specific seat selection

Data Model

events (id, name, venue_id, start_time, artist_id, ...)
venues (id, name, address, capacity, seat_map_url)
seats (id, event_id, section, row, number, status, held_by, held_until, sold_to)
orders (id, user_id, event_id, total, status, created_at)
order_items (id, order_id, seat_id, price)
tickets (id, order_id, seat_id, qr_code_hash, issued_at)

Search and Discovery

  • Full-text search: Elasticsearch for artist name, event name, venue search
  • Geo search: PostGIS or Elasticsearch geo queries for “events near me”
  • Faceted filters: date range, price range, category, availability
  • Trending: pre-computed trending events updated every 5 minutes from purchase velocity

Ticket Validation (Anti-Fraud)

  • Each ticket has a unique QR code containing signed JWT (venue ID, event ID, seat ID, expiry)
  • Scanner app verifies signature offline or via low-latency API call
  • Mark ticket as scanned in Redis to prevent reuse
  • Detect suspicious patterns: same ticket scanned twice, bulk purchases from single account

Interview Checklist

  • Lead with the double-booking problem — it’s the crux; explain optimistic locking or Redis lock
  • Describe the hold → payment → confirmed flow with TTL expiry
  • Address traffic spike: virtual waiting room is the industry standard answer
  • Separate read (search/browse) from write (reservation) paths
  • Mention ticket validation with signed QR codes

  • Coinbase Interview Guide
  • Lyft Interview Guide
  • Shopify Interview Guide
  • Uber Interview Guide
  • Airbnb Interview Guide
  • Stripe Interview Guide
  • {
    “@context”: “https://schema.org”,
    “@type”: “FAQPage”,
    “mainEntity”: [
    {
    “@type”: “Question”,
    “name”: “How do you prevent double-booking in a ticket reservation system?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “Use optimistic locking: each seat row has a version number. Read the seat and its version, then update with WHERE version = expected_version. If 0 rows are affected, another user reserved it first—show an error. Alternatively, use a Redis distributed lock (SET NX EX) per seat: only the process that acquired the lock can proceed with reservation. The database constraint (status check) acts as a final safety net regardless of the locking approach.” }
    },
    {
    “@type”: “Question”,
    “name”: “How does a virtual waiting room work for high-traffic ticket on-sales?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “When a popular event goes on sale, a virtual waiting room queues incoming users before letting them access the purchase flow. Users are assigned a queue position and see an estimated wait time. The system admits N users per minute based on backend capacity. Users get a time-limited session token to complete their purchase. This prevents the backend from being overwhelmed, ensures fairness, and is more reliable than first-come-first-served under thundering herd conditions.” }
    },
    {
    “@type”: “Question”,
    “name”: “How do you implement seat holds with automatic expiry in a ticket system?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “When a user selects a seat, create a hold record in Redis with a TTL (typically 10 minutes): SET seat:{event_id}:{seat_id}:held {user_id} EX 600. Also update the seat status in the database to 'held' with a held_until timestamp. A background job (or DB scheduled query) releases seats where held_until has passed and status is still 'held'. When the user completes payment, atomically update status to 'sold' before the hold expires.” }
    }
    ]
    }

    Scroll to Top