Business Systems12 min read

Design a Ticket Booking System

Sell out stadiums without selling a seat twice
scope:Real-World Systemdifficulty:Advanced

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.
β–Έ The idea: browse β†’ select β†’ hold β†’ purchase

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.

β–Έ Seat locking: preventing double-booking
Click chart to zoom
Booking flow: the Redis lock ensures only one user can hold a seat at a time, with automatic expiry if they abandon checkout

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.

β–Έ Virtual waiting room for flash sales

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.

β–Έ Full architecture
Note: Interview tip: The virtual waiting room pattern is critical for fairness in flash-sale scenarios. It's the difference between a system that crashes under load and one that gracefully handles 100K concurrent users. Always mention it when discussing ticket booking, limited-edition drops, or any system with extreme burst traffic.

Key Metrics

Seat availability check
Redis lookup per seat
< 5 ms \(O(1)\)
Booking success rate
15% fail due to contention on hot seats
~85% β€”
Peak TPS (with waiting room)
Queue absorbs burst, steady flow to backend
50K+ β€”
Hold timeout
Auto-release via Redis TTL
10 min β€”

Quick check

Why is a Redis distributed lock preferred over database-level optimistic locking for seat holds in high-concurrency scenarios?

Continue reading