Skip to content
AI360Xpert

Ticket Booking System

Intermediate

Overview

A ticket booking system reserves finite inventory - a specific seat, hotel room, or event ticket - under heavy concurrent demand. The defining challenge is preventing double-booking: two users must never be sold the same seat, even when thousands click "buy" for a hot concert in the same second. This is fundamentally a concurrency and consistency problem, not a raw-throughput one.

High-level architecture for Ticket Booking System
High-level architecture for Ticket Booking System

Functional Requirements

  • Browse events/venues and see seat availability.
  • Hold selected seats for a short window while the user pays.
  • Confirm a booking after successful payment; release the hold if payment fails or times out.
  • Cancel/refund a booking and return the seat to inventory.

Non-Functional Requirements

  • Strong consistency on inventory: a seat is sold at most once - no double-booking, ever.
  • Availability for browsing: read-heavy browsing must stay up even under load.
  • Handle spikes: a popular on-sale creates massive contention on a small set of seats.
  • Fairness: users who commit first should win; avoid indefinite starvation.

Capacity Estimation

Assume a hot event with 100K seats and 1M users rushing at on-sale.

  • Read QPS: browsing/availability checks dominate — potentially hundreds of thousands/sec during on-sale, mostly cacheable.
  • Write QPS (holds/bookings): bounded by inventory — only 100K seats can ever be sold, so successful writes are capped, but attempts massively exceed supply, creating contention on the same rows.
  • Storage: modest — events, seats, bookings are small structured records (GBs, not TBs).

The insight: this is a low-write, high-contention problem. The scale challenge is concurrent contention for scarce rows, not data volume.

High-Level Architecture

The system is divided into a highly-cached Search/Browse Service for displaying available seats, and a strongly consistent Booking Service for transactions. Redis is used heavily to manage temporary seat locks (holds) with TTLs. A relational database (PostgreSQL) acts as the source of truth for final inventory and uses ACID transactions to prevent double-booking.

Data Model

EntityFields / SchemaStorage Choice
event
event_id (PK), venue_id, start_time
Relational, cached
seat
seat_id (PK), event_id, status (available/held/booked), version
Strongly consistent relational store (PostgreSQL)
hold
hold_id, seat_id, user_id, expires_at
Redis with TTL (auto-expire)
booking
booking_id (PK), user_id, seat_ids, status, payment_id, idempotency_key
Relational, ACID

Detailed Design

The Two-Phase Reserve-Then-Confirm Flow

Booking is split into a temporary hold and a permanent confirmation:

  1. Hold: the user selects seats; the system atomically marks them held in Redis with a short TTL (e.g., 5–10 minutes). This takes the seats off the market while the user inputs credit card details.
  2. Confirm: after payment succeeds, the system writes the final booking to the DB and deletes the Redis hold. If payment fails or the user abandons checkout, the Redis TTL lapses, and the seats automatically return to available without any background cleanup scripts.

Preventing Double-Booking (The Crux)

The atomic seat claim must be safe under concurrency. Options, in order of preference:

  • Conditional Update / Optimistic Locking: UPDATE seat SET status='held', version=version+1 WHERE seat_id=? AND status='available' AND version=old_version. If zero rows change, someone beat you - return "seat taken." This needs no long-held database lock and scales beautifully.
  • Pessimistic Lock: SELECT ... FOR UPDATE on the seat rows within a transaction. Correct, but holds locks and can create contention/deadlocks on hot seats.

Read Path and Caching

Availability browsing is served from cache and read replicas. It can be slightly stale ("almost sold out") because the authoritative check happens at hold time against the strongly consistent inventory DB. This keeps the massive read load off the transactional store.

Bottlenecks & Solutions

During a Taylor Swift-level on-sale, 10 million users hit the "Buy" button at exactly 10:00:00 AM. A relational DB will instantly crash if 10 million connections try to `UPDATE` the same 50,000 seat rows. To fix this, you must introduce a Virtual Waiting Room. The load balancer routes 99.9% of users into a static queue page, and only lets a trickle of users (e.g., 500/sec) through to the actual booking API to match the DB's throughput capacity.

Interview Follow-up Questions

Q: How do you handle a user reserving seats, the internet dropping, and the user trying to reserve them again?

The 'Reserve' API must be idempotent. The client sends a unique `request_id`. If the server sees a hold with that `request_id` already exists, it simply returns success and the remaining TTL time, rather than rejecting the request as 'seats taken'.

Q: What happens if the Redis instance holding the temporary locks crashes?

If Redis crashes, holds are lost, and seats become available for others to grab. To prevent a user successfully paying for a seat that Redis forgot they held, the Final Confirmation step must validate the seat's status in the strongly-consistent Database *before* charging the credit card.