Design Ticketmaster (Ticket Booking)
DifficultyMedium | HelloInterview: problem breakdown
Problem Statement
Design a ticket booking system where users can search for events, view seat maps, select seats, and purchase tickets. The key challenge: prevent double-booking under high concurrent demand for popular events.
๐Real-world: The Taylor Swift "Eras Tour" presale (November 2022) is the canonical Ticketmaster horror story โ the system took 3.5 billion requests (4ร its prior peak), much of it from bots and scalpers, the queue collapsed, and the public sale was cancelled, triggering a U.S. Senate antitrust hearing. The lesson it teaches is the one this design is really about: the hard problem isn't the database lock that prevents double-booking โ it's surviving a thundering herd where most traffic is hostile. That makes it a security design as much as a scaling one: the real defenses are a virtual waiting room / queue to shed load, aggressive bot detection (scalper bots vs humans), and rate limiting โ the seat-reservation TTL lock is necessary but it's the easy 20%. Interviewers reward candidates who bring up the bot/abuse layer unprompted.
Requirements
Functional
- Browse and search events by location, date, category
- View seat map with real-time availability
- Select specific seats and hold them for a checkout window
- Complete purchase with payment
- View booked tickets
Non-Functional
- 10M events, 1B tickets
- Flash sales: 500k concurrent users trying to book a popular event in seconds
- Seat hold must be exclusive โ a seat cannot be booked by two users simultaneously
- Seat hold timeout: 10 minutes (if user abandons checkout, seat is released)
- Read-heavy under normal conditions; write-spike on popular event sales
Core Design
The Central Challenge: Inventory Lock
Seat booking is a critical section: select a seat, mark it as held, charge the user โ all atomically without double-booking.
Approach 1: Database Optimistic Locking
-- Read current status
SELECT status FROM seats WHERE seat_id = ? AND status = 'available'
-- Update only if still available (OCC โ optimistic concurrency control)
UPDATE seats SET status='held', user_id=?, held_until=NOW()+600
WHERE seat_id = ? AND status = 'available'
-- Check affected rows: if 0 โ someone else grabbed it first- Good for low-contention; degrades under high contention (retry storms)
Approach 2: Redis Distributed Lock + DB Write (recommended for flash sales)
lock_key = f"seat_lock:{seat_id}"
lock_value = str(uuid4()) # unique to this request
# Acquire lock: SET NX PX 10000 (10 second lock)
acquired = redis.set(lock_key, lock_value, nx=True, px=10000)
if not acquired:
return "seat not available"
# Inside lock: write to DB
db.execute("UPDATE seats SET status='held', user_id=? WHERE seat_id=? AND status='available'")
# Release lock: Lua script to ensure only owner releasesApproach 3: Queue-based serialization (best for very hot seats)
- All seat requests for a popular event go into a queue (per event)
- Single consumer processes requests in order โ no concurrency issue
- Users see a "queue position" virtual waiting room
- Used by Ticketmaster, EventBrite for sold-out events
Architecture
Users (browsers/apps)
โ
API Gateway (rate limit per IP, auth)
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Search Service โ โ Elasticsearch (event search)
โ Event Service โ โ PostgreSQL (event/venue data)
โ Seat Service โ โ PostgreSQL (seat inventory)
โ Booking Service โ โ PostgreSQL + Redis locks
โ Payment Service โ โ Stripe / payment processor
โ Notification Service โ โ Email, SMS, push
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
PostgreSQL Redis Cluster
(source of truth) (locks + seat status cache)Database Design
events(event_id, name, venue_id, date, ...)
venues(venue_id, name, capacity, seating_map_json, ...)
seats(
seat_id UUID PRIMARY KEY,
event_id UUID REFERENCES events,
section TEXT,
row TEXT,
number INT,
status ENUM('available','held','sold'),
held_by UUID, -- user_id
held_until TIMESTAMP, -- null if available or sold
price DECIMAL
)
bookings(booking_id, user_id, event_id, seat_ids[], payment_id, status, created_at)Seat Hold + Release Flow
1. User selects seats (optimistic: UI shows available)
2. POST /hold โ Booking Service
- Acquire Redis lock per seat_id (10s TTL)
- UPDATE seats SET status='held', held_until=NOW()+600
- Return hold_id + expiry timer
3. User completes payment (within 10 min)
- Payment Service charges card
- On success: UPDATE seats SET status='sold'
- Release Redis lock
4. If user abandons:
- Background job: find seats WHERE status='held' AND held_until < NOW()
- UPDATE seats SET status='available', held_by=NULL
- OR: Redis lock TTL expires โ next requester can grab itRead Scalability
Seat availability reads: cache in Redis (seat:{seat_id}:status) with 1-second TTL. Users see near-real-time availability without hitting DB on every request. On status change, invalidate cache.
Seat map display: pre-render SVG/JSON map per event; cache in CDN; update only seats that change (delta updates via WebSocket).
Key Design Decisions
| Decision | Choice | Reason |
|---|---|---|
| Concurrency control | Redis lock + DB write | Fast lock acquisition; DB is source of truth |
| Flash sale queue | Virtual waiting room | Smooths demand spike; fairness for users |
| Seat status cache | Redis (1s TTL) | Reduce DB reads under high concurrent read |
| Payment integration | External processor (Stripe) | Don't build payments; handle webhook callbacks |
| Checkout timeout | 10 minutes with background GC | Standard industry practice |
Security Considerations
| Threat | Mitigation |
|---|---|
| Bot scalping | CAPTCHA at seat selection; device fingerprinting; behavioral analysis (click speed) |
| Race condition / double booking | Redis distributed lock; DB-level uniqueness constraint on (event_id, seat_id, status=sold) |
| Payment fraud | PCI-DSS compliant payment processor; 3DS2 authentication for high-value orders |
| Account takeover โ ticket theft | 2FA on accounts; email confirmation for bookings; re-auth for high-value purchases |
| Seat hoarding | Limit seats per transaction per account; hold timeout enforcement |
| API enumeration of seat availability | Rate limit; return aggregate counts, not individual seat IDs unless in booking flow |
Interview Tips
spend the most time on it.
is a real pattern used in production (Ticketmaster uses it) โ mentioning it shows industry knowledge.
two-phase: hold (reserve for checkout) โ sold (payment confirmed). If payment fails, seat returns to available.
Stripe/payment processor sends payment.succeeded webhook โ you need to handle this idempotently (replay safe).