Design Ticket Booking: Seat Holds, Concurrency and Payments
Solve the ticket booking system design interview: seat maps, temporary holds, concurrency control, payment flow, queues, scaling, and tradeoffs.
Quick answer: Design Ticket Booking: Seat Holds, Concurrency and Payments
Design Ticket Booking: Seat Holds, Concurrency and Payments is covered here as a practical system design interview problem. Solve the ticket booking system design interview: seat maps, temporary holds, concurrency control, payment flow, queues, scaling, and tradeoffs. Use it to practice requirements, APIs, storage choices, scaling bottlenecks, data consistency, observability, and failure handling for a hard interview round.
Focus on the problem being solved, the main design choice, the failure mode, and the tradeoff you would defend in front of a senior engineer. This page is part of the DosiBridge system design knowledge graph, so it links related concepts, interview problems, tradeoffs, and references instead of leaving the topic isolated.
Problem Statement
Design a ticket booking system like Ticketmaster or BookMyShow for events (concerts, movies, sports). Users browse events, view a venue seat map, select and temporarily hold seats, complete payment, and receive e-tickets. The system must handle extreme concurrency during on-sale moments when millions of users compete for limited seats.
Requirements
Functional
- Browse events by category, location, and date; view venue seat maps with availability
- Select seats: temporarily hold selected seats for 7 minutes during checkout
- Complete booking: charge payment and issue e-tickets (QR code) atomically
- Waiting room/queue: during high-demand events, queue users and admit in controlled batches
Non-Functional
- Consistency: No double-booking -- a seat is sold to exactly one customer
- Scale: 10M concurrent users during major on-sale events, 50K seat-selection attempts/second
- Latency: Seat map loads in <1 second, seat hold confirmation in <500ms
- Fairness: First-come-first-served with a virtual queue to prevent system overload
Core Architecture
-
Seat Inventory Service -- Manages seat state per event: AVAILABLE, HELD, BOOKED. Uses Redis for real-time seat map state (hash map: seat_id -> status). Seat holds use Redis SETEX with a 7-minute TTL -- automatically released if checkout is not completed. The atomic Lua script: check seat is AVAILABLE, set to HELD with TTL, return success/failure.
-
Virtual Waiting Room -- During high-demand on-sales, users are placed in a queue (backed by Redis sorted set with join timestamp as score). A rate controller admits N users/second to the seat selection page. Users see their position and estimated wait time. This prevents the thundering herd from overwhelming the seat inventory service.
-
Checkout and Payment Service -- Once seats are held, the user has 7 minutes to complete payment. Flow: validate hold is still active, charge payment via Stripe/payment gateway, atomically transition seats from HELD to BOOKED in a database transaction, generate e-ticket with QR code, send confirmation email. If payment fails, seats revert to AVAILABLE (TTL expiry or explicit release).
- Event Management Service -- CRUD for events, venues, and seating layouts. Venues have reusable seat map templates (e.g., "Madison Square Garden Layout A"). Events are created by linking a venue layout with pricing tiers (Section A = $200, Section B = $150). Handles event scheduling, artist info, and category tagging.
Database Choice
Redis for real-time seat availability and holds -- atomic operations (Lua scripts) prevent race conditions at 50K ops/second. TTL-based holds auto-expire without cleanup jobs. PostgreSQL for permanent booking records, user accounts, event metadata, and payment records -- ACID for the final booking transaction. Kafka for booking events consumed by email notification, analytics, and fraud detection services.
Key API Endpoints
GET /api/v1/events/\{event_id\}/seats
-> Returns: \{ sections: [\{ name: "A", seats: [\{ id: "A-15", status: "available", price: 200 \}] \}] \}
POST /api/v1/events/\{event_id\}/hold
-> Body: \{ seat_ids: ["A-15", "A-16"], session_id: "S-abc" \}
-> Returns: \{ hold_id: "H-789", expires_at: "...", seats: ["A-15", "A-16"] \}
POST /api/v1/bookings
-> Body: \{ hold_id: "H-789", payment_method_id: "PM-1" \}
-> Returns: \{ booking_id: "BK-456", seats: ["A-15", "A-16"], qr_code_url: "...", total: 400.00 \}
Scaling Insight
The Redis-based seat hold with TTL is the critical design choice. Using database row locks for seat holds would create massive contention during on-sale events (50K concurrent seat-selection attempts). Redis handles this with single-threaded atomic operations at 100K+ ops/second. The 7-minute TTL auto-releases abandoned holds without any background cleanup job. Combined with the virtual waiting room (which limits concurrency to a manageable 5K users simultaneously selecting seats), the system handles million-user on-sale events smoothly.
Key Tradeoffs
| Decision | Option A | Option B | Chosen |
|---|---|---|---|
| Seat hold mechanism | Database row lock | Redis SETEX with TTL | Redis -- 10x throughput, automatic expiry, no lock contention |
| On-sale traffic management | Let everyone in (hope for the best) | Virtual waiting room | Waiting room -- controlled admission prevents system overload, fairer experience |
| Seat map rendering | Server-side rendered image | Client-side SVG with data overlay | Client-side SVG -- interactive, real-time status updates via WebSocket, lower server cost |
Original DosiBridge notes for Design Ticket Booking: Seat Holds, Concurrency and Payments
This section is written specifically for this page, not copied from a general system-design checklist. The useful way to study Design Ticket Booking: Seat Holds, Concurrency and Payments is to connect the concept to a scarce or money-moving workflow where duplicate writes and race conditions are more dangerous than slow responses. That framing changes the design discussion from definitions to operating choices.
What makes this topic distinct
- Core Entity: For Design Ticket Booking: Seat Holds, Concurrency and Payments, decide the core entity before choosing tools; otherwise the design looks clean but fails when ticket changes.
- Read/Write Path: For Design Ticket Booking: Seat Holds, Concurrency and Payments, decide the read/write path before choosing tools; otherwise the design looks clean but fails when booking changes.
- Contention Point: For Design Ticket Booking: Seat Holds, Concurrency and Payments, decide the contention point before choosing tools; otherwise the design looks clean but fails when ticket changes.
- Abuse Path: For Design Ticket Booking: Seat Holds, Concurrency and Payments, decide the abuse path before choosing tools; otherwise the design looks clean but fails when booking changes.
- Recovery Workflow: For Design Ticket Booking: Seat Holds, Concurrency and Payments, decide the recovery workflow before choosing tools; otherwise the design looks clean but fails when ticket changes.
Design Ticket Booking: Seat Holds, Concurrency and Payments correctness boundary
For this problem, separate the user-visible action from background work. The correctness boundary is the smallest state transition that must not be duplicated, lost, or reordered. Naming that boundary makes the solution more original than a stock API/database/cache template.
Design Ticket Booking: Seat Holds, Concurrency and Payments scaling bottleneck
The first bottleneck should be described in the language of this system: a hot timeline, a contested seat, a busy wallet ledger, a media-processing queue, or a search index update delay. That bottleneck decides which component deserves the deepest interview discussion.
Production review question
If Design Ticket Booking: Seat Holds, Concurrency and Payments suddenly receives 10x traffic, which entity becomes contested first, and what queue, lock, shard, or cache protects that entity without hiding correctness bugs?
Practical metric to track
Track ticket-booking health as a named dashboard or review item: combine one user-facing metric, one saturation metric, and one correctness metric. For Design Ticket Booking: Seat Holds, Concurrency and Payments, this keeps the discussion grounded in behavior that can be measured after deployment instead of generic advice that sounds good in an interview but never gets verified.
Sources
Unique design focus: scarce inventory and fairness under spikes
Ticket booking is not a normal ecommerce checkout because the inventory is scarce, highly contested, and time-sensitive. The main design problem is preventing oversell while keeping the experience fair when thousands of users try to reserve the same seats at the same second.
Core entities
- Event, venue, section, row, seat.
- Seat state: available, held, sold, blocked, released.
- Hold: user ID, seat IDs, expiry timestamp, idempotency key.
- Order: payment state, fulfillment state, ticket delivery state.
- Queue position or admission token for high-demand drops.
Seat hold flow
- User enters a waiting room before sale starts.
- Admission service gives a signed token to a controlled number of users.
- User selects seats; inventory service attempts an atomic hold.
- Held seats receive a short TTL such as 5-10 minutes.
- Payment succeeds and converts hold to sold.
- Expired holds are released by a background worker.
Consistency choice
For seat ownership, strong consistency matters. Two users must not buy the same seat. This usually means a transactional database constraint, row-level locking, compare-and-set operation, or a single-writer partition for a seat group. Other parts of the system, such as email delivery, analytics, recommendations, and search indexing, can be eventually consistent.
Spike protection
- Waiting room limits traffic before it reaches inventory.
- Rate limits protect APIs from bots and refresh storms.
- Signed admission tokens stop users from bypassing the queue.
- Idempotency keys prevent duplicate orders during retries.
- Seat maps can be cached, but seat state must refresh or stream updates.
Failure handling
If payment succeeds but ticket fulfillment fails, the order should remain paid and retry fulfillment. If payment times out, the hold should remain until expiry or confirmed failure. If the user refreshes checkout, the idempotency key should return the existing order or hold instead of creating a duplicate.
Interview tradeoff
The strongest answer separates scarce-seat consistency from the rest of the architecture. Use strict correctness for the final seat transition, but use queues, caching, and eventual processing for everything that does not decide who owns the seat.