Skip to content

Ticket Booking System Design (HLD & LLD)

A ticket booking system (such as BookMyShow, airline, or train reservation systems) must support high concurrency while ensuring that the same seat is never booked twice. This document covers the functional requirements, architecture, database schema, concurrency control, APIs, scaling strategies, and implementation examples.

  1. Search events, movies, flights, or trains
  2. View seat availability
  3. Select seats
  4. Lock seats temporarily
  5. Process payment
  6. Confirm booking
  7. Cancel booking
  8. View booking history
  9. Send notifications (email/SMS)
  1. Add events/shows
  2. Manage theaters/flights/trains
  3. Manage pricing
Requirement Explanation
High availability Booking should not fail
Strong consistency Prevent double booking
High concurrency Thousands of users booking simultaneously
Low latency Fast seat selection
Fault tolerance Handle payment failures gracefully
Scalability Support millions of users

Assumptions:

  • 10 million users/day
  • Peak traffic: 5,000 requests/sec
  • Bookings: 500/sec
  • 100-500 seats per show

The primary technical challenge is preventing multiple users from booking the same seat.

  • API Gateway
  • User Service
  • Search Service
  • Booking Service
  • Seat Inventory Service
  • Payment Service
  • Notification Service
  • Show/Event Service
flowchart TD
    U[Users] --> G[API Gateway]
    G --> US[User Service]
    G --> SS[Search Service]
    G --> BS[Booking Service]
    G --> PS[Payment Service]
    G --> NS[Notification Service]
    BS --> SI[Seat Inventory Service]
    SI --> DB[(Database)]
    SI --> R[(Redis Cache)]

Typically implemented using:

  • Elasticsearch
  • Redis

Example query:

Search Movies
Location = Bangalore
Date = Today

Responsible for preventing double booking.

Possible approaches:

  • Pessimistic locking
  • Optimistic locking
  • Distributed locking
  • Queue-based booking

A common production approach is Seat Locking + TTL.

sequenceDiagram
    participant User
    participant Booking
    participant Inventory
    participant Payment
    participant DB

    User->>Booking: Select seats
    Booking->>Inventory: Lock seats
    Inventory-->>Booking: Locked (TTL = 5 min)
    Booking->>Payment: Process payment
    alt Payment Success
        Payment->>Booking: Success
        Booking->>DB: Save booking
        Booking->>Inventory: Mark seats BOOKED
    else Payment Failed
        Payment->>Booking: Failure
        Booking->>Inventory: Release seats
    end
AVAILABLE
LOCKED
BOOKED

Locks are typically stored in Redis with a 5-minute TTL.

User
-----
user_id
name
email
phone
Event
------
event_id
name
language
duration
Theater
-------
theater_id
name
location
Show
----
show_id
event_id
theater_id
start_time
end_time
Seat
----
seat_id
show_id
seat_number
type
price
status

Seat status:

AVAILABLE
LOCKED
BOOKED
Booking
-------
booking_id
user_id
show_id
status
total_price
created_at

Booking status:

PENDING
CONFIRMED
FAILED
CANCELLED
BookingSeat
-----------
id
booking_id
seat_id
price
SeatLock
--------
seat_id
user_id
lock_expiry

Stored in Redis.

Example:

Seat A1 locked by user123
Expiry = now + 5 min
erDiagram
    USER ||--o{ BOOKING : places
    EVENT ||--o{ SHOW : has
    THEATER ||--o{ SHOW : hosts
    SHOW ||--o{ SEAT : contains
    BOOKING ||--o{ BOOKING_SEAT : includes
    SEAT ||--o{ BOOKING_SEAT : booked_as

Two users may attempt to book the same seat simultaneously.

Add a version column.

Seat
----
seat_id
status
version

SQL update:

UPDATE seat
SET status = 'BOOKED'
WHERE seat_id = 'A1'
AND version = 2;
Terminal window
SETNX seat:A1 lock

Use Kafka or RabbitMQ to serialize booking requests.

@PostMapping("/book")
public BookingResponse bookSeats(@RequestBody BookingRequest request) {
return bookingService.book(request);
}
public BookingResponse book(BookingRequest request) {
seatService.lockSeats(request.getSeatIds());
PaymentResponse payment = paymentService.processPayment();
if (payment.isSuccess()) {
bookingRepository.save();
seatService.confirmSeats();
} else {
seatService.releaseSeats();
}
}

Redis example:

seat:A1 -> user123
TTL = 5 minutes

Expired locks automatically transition seats back to AVAILABLE.

Common strategies:

  1. Atomic database updates
  2. Distributed locking
  3. Single-writer principle
  4. Queue-based processing

Typical production stack:

Redis + Queue + Database
  • Read replicas
  • Redis cache
  • CDN

Shard by:

  • show_id
  • event_id
  • theater_id

Example:

Show 1 -> DB1
Show 2 -> DB2

Cache:

  • Seat availability
  • Show data
  • Search results

Technology:

Redis

Release locked seats.

Use:

  • Retry
  • Circuit Breaker
  • Dead Letter Queue

Technologies:

  • Resilience4j
  • Kafka

Monitoring:

Prometheus
Grafana

Tracing:

Micrometer Tracing
Zipkin
GET /events?city=bangalore
GET /shows/{showId}/seats
POST /seats/lock
POST /booking

Users can join a waiting queue if seats are sold out.

Pricing can vary based on:

  • Demand
  • Time
  • Popularity

Use:

  • Rate limiting
  • CAPTCHA
  • Queue systems
Component Technology
API Gateway Kong / Nginx
Backend Java Spring Boot
Search Elasticsearch
Cache Redis
Messaging Kafka
Database MySQL / Cassandra
Monitoring Prometheus

A clear explanation order:

  1. Requirements
  2. High-Level Design
  3. Seat Locking
  4. Concurrency Handling
  5. Database Design
  6. Scaling Strategy
  7. Failure Handling

Seat locking and concurrency control are the most critical interview topics.

  • Strong consistency is essential to prevent double booking.
  • Redis-based seat locks with TTL provide fast, temporary reservation handling.
  • Queue-based processing improves correctness under heavy contention.
  • Scale reads with caching and replicas; scale writes using sharding.
  • Build resiliency using retries, circuit breakers, and asynchronous messaging.