System Design Interview: Design a Ticketing System (Ticketmaster)

System Design Interview: Design a Ticketing System (like Ticketmaster)

Designing a ticket booking system like Ticketmaster tests your ability to handle high concurrency, flash sale scenarios, inventory management, and payment processing. The core challenge is preventing overselling while maintaining high availability.

Requirements Clarification

Functional Requirements

  • Browse events and view available seats
  • Select and reserve seats for a limited hold period (10 minutes)
  • Complete purchase with payment processing
  • Handle extremely high traffic during popular event on-sales (flash sales)
  • View and cancel existing bookings

Non-Functional Requirements

  • Scale: 100K events, 50K seats/event, 500K concurrent users during peak
  • Latency: seat selection < 200ms, checkout < 2s
  • Consistency: never oversell (strong consistency for seat inventory)
  • Availability: 99.99% uptime

Core Challenge: Seat Inventory

The hardest part is concurrent seat selection. Two users cannot book the same seat. Options:

  1. Optimistic locking: read seat status, update with version check – retry on conflict
  2. Pessimistic locking: lock row in DB during selection – low throughput
  3. Redis atomic operations: SETNX for seat hold – fast, but need DB sync
  4. Virtual queue: queue users during peak, process sequentially – used by Ticketmaster

High-Level Architecture

Users -> CDN -> Load Balancer
              |
       API Gateway
       |           |           |
  Browse        Queue        Checkout
  Service       Service      Service
       |           |           |
  Event DB    Redis Queue   Payment
  (Postgres)  + Seat Lock   Gateway
                   |
              Booking DB
              (Postgres)
                   |
              Notification
              Service (email/SMS)

Seat Reservation Flow

1. User selects seat
2. API calls Redis: SET seat:{event_id}:{seat_id} {user_id} EX 600 NX
   - NX = only set if Not eXists (atomic)
   - EX 600 = auto-expire in 10 minutes (hold period)
   - Returns OK (reserved) or nil (already taken)
3. On success: persist hold to DB with expiry timestamp
4. User completes checkout within hold window
5. Payment charged via Stripe/Braintree
6. Mark seat as SOLD in DB, release Redis key (or let TTL expire)
7. Send confirmation email/SMS

Virtual Queue for Flash Sales

When traffic spikes (popular concert goes on sale), use a virtual waiting room:

1. Users enter waiting room URL before sale starts
2. Backend assigns each user a random position token
3. At sale start, release users in batches (e.g., 1000 every 30s)
4. Token validated at API gateway - non-queued users rejected
5. Users behind the gate see estimated wait time

Implementation: Redis sorted set (ZADD queue:event123 {timestamp} {user_token}), ZPOPMIN to release batches. This prevents thundering herd from hitting seat inventory directly.

Database Schema

events: id, name, venue_id, start_time, on_sale_time, total_seats
seats: id, event_id, section, row, number, status, hold_expires_at
  status: AVAILABLE | HELD | SOLD
bookings: id, user_id, event_id, total_amount, status, created_at
booking_seats: booking_id, seat_id
payments: id, booking_id, stripe_payment_intent_id, amount, status

Handling Expired Holds

Three approaches for releasing expired seat holds:

  • Redis TTL: Redis auto-expires the lock key; background job scans DB for holds past expiry and marks seats AVAILABLE
  • Cron job: Every 30s, UPDATE seats SET status=AVAILABLE WHERE status=HELD AND hold_expires_at < NOW()
  • Event-driven: Redis keyspace notifications on expiry trigger a Lambda/worker to release seat in DB

Read Scalability for Seat Maps

  • Cache seat map in Redis as a bitmap or hash: O(1) lookup per seat, invalidate on state change
  • Use read replicas for event browse and seat map reads
  • CDN-cache static event assets (images, venue maps) aggressively
  • WebSocket or SSE for real-time seat status updates (seats turning red as others select them)

Payment Processing

  1. Create Stripe PaymentIntent on checkout page load (idempotency key = booking_id)
  2. Client confirms with card details – Stripe handles PCI scope
  3. Stripe webhook on payment_intent.succeeded marks booking CONFIRMED
  4. If payment fails, release seat hold immediately
  5. Idempotency keys prevent double charging on retries

Consistency Trade-offs

  • Seat inventory: strong consistency – use distributed locks (Redis NX) + DB transactions
  • Event catalog: eventual consistency acceptable – cache aggressively
  • Booking history: strong consistency (user must see confirmed bookings immediately)
  • Notification delivery: at-least-once via message queue (idempotent email handler)

Interview Tips

  • Lead with the inventory consistency problem – it is the core challenge
  • Explain Redis SETNX for atomic seat holds
  • Discuss virtual queue as a traffic management pattern for flash sales
  • Know the difference between hold and confirmed booking states
  • Mention idempotency in payment processing to prevent double charges
  • Discuss real-time seat map updates via WebSocket

Scroll to Top