Design a Ticket Booking System
The Problem
Imagine Taylor Swift announces a world tour. Millions of fans hit the site at the same moment, all competing for a limited number of seats. Your system must sell tickets for events with flash-sale level concurrency β thousands of users competing for the same seats simultaneously.
Functional Requirements
- Browse events: Users search and discover concerts, sports, theater shows by date, location, and category.
- Select seats: Interactive venue map showing available, held, and sold seats in real time.
- Hold/Reserve: Temporarily lock a seat for a user (10-minute TTL) while they complete payment.
- Purchase: Process payment and confirm the booking atomically.
- Issue tickets: Generate digital tickets (QR codes, barcodes) and send confirmation via email/SMS.
Non-Functional Requirements
- No double-booking: Two users must never purchase the same seat. This is the #1 invariant.
- Handle 10K+ TPS burst: Hot events like concert drops generate massive traffic spikes.
- Fair queuing: Prevent bots from scooping up all tickets β real fans deserve a fair chance.
- Timeout for held seats: If a user abandons checkout, their held seats must be released automatically.
Back-of-the-Envelope Estimation
Let's size the system:
- 50M events/year across all venues (concerts, sports, theater, conferences).
- 500M tickets sold/year β an average of 10 tickets per event.
- Peak: 100K concurrent users for a single hot event (major artist, championship game).
- Seat selection TTL: 10 minutes β users must complete payment within this window or the hold expires.
- Storage: ~500 bytes per ticket record Γ 500M = ~250 GB/year of ticket data.
- Read-heavy: Seat availability checks outnumber purchases 100:1 during a sale.
The critical insight: the system is bursty. 99% of the time traffic is modest. But when a hot event goes on sale, you face a thundering herd of 100K+ users in seconds.
Seat Inventory Model
The core data model is hierarchical: Event β Sections β Rows β Seats. Each seat has a state machine:
- Available: No one has claimed this seat. Shown as green on the venue map.
- Held: A user is in the checkout process. Shown as yellow. Has a TTL (10 min) β if payment isn't completed, it reverts to available.
- Sold: Payment confirmed. Shown as red. Permanent state.
The transition from available β held is the critical race condition. Two users clicking the same seat at the same millisecond must not both succeed.
Concurrency solutions:
- Optimistic locking: Read the seat version, attempt update with
WHERE version = X. If another transaction already changed it, your update fails and you retry or pick another seat. - Distributed locks (Redis): Acquire a lock on the seat key (
SETNX seat:event123:A42) with a TTL. Only the lock holder can modify the seat state.
Redis locks are preferred for high-concurrency scenarios because they avoid database contention entirely.
Concurrency Control
The hardest part of a ticket booking system isn't the CRUD β it's handling 100K users hitting "Buy" at the same instant.
Redis Distributed Locks with TTL
For seat holds, we use Redis SETNX (set-if-not-exists) with a 10-minute TTL. This gives us atomic, distributed locking:
SETNX seat:event123:A42 user456 EX 600β only succeeds if no one else holds it.- The TTL ensures abandoned holds auto-release β no manual cleanup needed.
- On payment success, we mark the seat as sold in the database and delete the Redis key.
Virtual Waiting Room (Queue-Based Admission)
For flash sales, even Redis locks aren't enough β 100K simultaneous lock attempts cause a stampede. Instead, we implement a virtual waiting room:
- When a hot event goes on sale, users enter a queue instead of hitting the booking page directly.
- Users are admitted in batches (e.g., 1,000 at a time) based on their queue position.
- Each admitted user gets a time-limited token to access the seat selection page.
- This converts a thundering herd into a controlled, steady flow.
Token Bucket for Fair Access
Combined with rate limiting (token bucket per IP/user), this prevents bots from monopolizing queue positions. CAPTCHA challenges further ensure fairness.
Additional Considerations
Anti-Bot Measures
Ticket scalping bots are a massive problem. Defenses include:
- CAPTCHA on queue entry β invisible reCAPTCHA or challenge-based.
- Device fingerprinting β detect multiple accounts from the same device.
- Purchase limits β max 4 tickets per user per event.
- Verified fan programs β pre-register interest, prioritize real fans.
Dynamic Pricing
Like airlines, ticket prices can adjust based on demand. Seats in high-demand sections increase in price as availability drops. This is implemented as a pricing service that factors in: section popularity, time until event, remaining inventory, and historical demand patterns.
Ticket Transfer & Resale
Tickets are often non-transferable to combat scalping, but official resale marketplaces allow controlled peer-to-peer transfers with price caps. Each ticket has a unique ID and ownership chain tracked in the database.