Design BookMyShow

02 Aug 2026, Updated: 10 Aug 2026 10 min read
1
A BookMyShow is an online ticket booking platform that allows users to search for movies, view show timings, select seats, make payments, and receive booking confirmations.

The system should support millions of concurrent users while preventing double booking, maintaining strong consistency during seat reservation, and providing high availability and low latency.

The platform should also handle highly concurrent booking traffic during popular movie releases, where thousands of users may attempt to reserve the same seats simultaneously.

Requirements

Functional Requirements

The BookMyShow system should satisfy the following functional requirements.

1. Allow users to search movies by city.
2. Allow users to view theatres and show timings.
3. Allow users to view seat availability.
4. Allow users to book one or more seats.
5. Allow users to make online payments.
6. Allow users to cancel bookings.
7. Send booking confirmations.

Non-Functional Requirements

The system should also satisfy the following non-functional requirements.

1. Low latency.
2. High availability.
3. Horizontal scalability.
4. Strong consistency during booking.
5. Fault tolerance.
6. Prevent double booking of seats.

API Design

The BookMyShow platform can expose REST APIs for movie search, show discovery, seat availability, and booking operations.

Search Movies

Request:
GET /api/v1/movies?city=Delhi
Response:
{
  "movies": [
    {
      "movieId": "M101",
      "title": "Avengers: Endgame",
      "language": "English",
      "duration": 181
    },
    {
      "movieId": "M102",
      "title": "Pushpa 2",
      "language": "Telugu",
      "duration": 190
    }
  ]
}

Get Show Seats

Request:
GET /api/v1/shows/S101/seats
Response:
{
  "showId": "S101",
  "seats": [
    {
      "seatId": "ST101",
      "seatNumber": "A1",
      "status": "AVAILABLE"
    },
    {
      "seatId": "ST102",
      "seatNumber": "A2",
      "status": "BOOKED"
    }
  ]
}

Book Seats

Request:
POST /api/v1/bookings

{
  "showId": "S101",
  "seatIds": ["ST101", "ST103"]
}
Response:
{
  "bookingId": "B12345",
  "status": "CONFIRMED"
}

High-Level Architecture

The following diagram illustrates the high-level architecture of a BookMyShow-like ticket booking platform.

The platform consists of services such as the Movie Inventory Service, Booking Service, Payment Service, Search Service, and Notification Service.

API Gateway

The API Gateway acts as the single entry point for client applications.

It authenticates requests, validates input, applies rate limiting, and routes requests to the appropriate backend service.

For example, movie search requests are routed to the Movie/Search Service, while seat booking requests are routed to the Booking Service.

The API Gateway also prevents unauthorized users from accessing protected booking and payment APIs.

Movie Inventory Service

The Movie Inventory Service manages movies, theatres, screens, show schedules, and seat inventory. It provides the inventory information required for movie discovery and seat booking.

All movie inventory data can be maintained in a relational database such as PostgreSQL.

The data is relatively stable compared with booking transactions, so frequently accessed movie, theatre, show, and seat information can be cached in Redis.

The MOVIE table stores movie information such as title, language, duration, and genre.
MOVIE
------------------------------------------------------------------
movie_id | title             | language | duration | genre
------------------------------------------------------------------
M101     | Avengers: Endgame | English  | 181      | Action
M102     | Pushpa 2          | Telugu   | 190      | Action
M103     | Dune: Part Two    | English  | 166      | Sci-Fi
The THEATRE table stores theatre and location information.
THEATRE
----------------------------------------------------
theatre_id | name           | city
----------------------------------------------------
T101       | PVR Phoenix    | Delhi
T102       | INOX City Mall | Mumbai
T103       | Cinepolis      | Bengaluru
The SCREEN table stores screens available within each theatre.
SCREEN
----------------------------------------------------
screen_id | theatre_id | name       | capacity
----------------------------------------------------
SC101     | T101       | Screen-1   | 200
SC102     | T101       | Screen-2   | 150
SC103     | T102       | Screen-3   | 180
The SHOW table stores scheduled movie screenings.
SHOW
-------------------------------------------------------------------------------------
show_id | movie_id | screen_id | start_time
-------------------------------------------------------------------------------------
S101    | M101     | SC101     | 2026-08-02 18:00
S102    | M102     | SC103     | 2026-08-02 20:00
S103    | M103     | SC201     | 2026-08-02 21:30
The SEAT table maintains the seat inventory for each show.
SEAT
-----------------------------------------------------------------------
seat_id | show_id | seat_number | status
-----------------------------------------------------------------------
ST101   | S101    | A1          | AVAILABLE
ST102   | S101    | A2          | BOOKED
ST103   | S101    | A3          | AVAILABLE
ST201   | S102    | B5          | BOOKED
ST301   | S103    | C10         | AVAILABLE
The seat state can be AVAILABLE, LOCKED, or BOOKED. The temporary LOCKED state prevents another user from selecting the same seat while the current user completes payment.

The Movie Inventory Service is the source of truth for movie, theatre, screen, show, and seat inventory.

It exposes APIs that allow the Booking Service to check seat availability, lock seats, confirm bookings, and release expired or cancelled locks.

Booking Service

The Booking Service manages the booking lifecycle, including seat selection, payment coordination, confirmation, and cancellation. The Movie Inventory Service remains the source of truth for seat availability and owns all seat state changes.

The seat locking and seat confirmation operations are separate database transactions. They are connected using a unique lockId. The Booking Service coordinates these operations with the payment workflow.

1. Lock Seats

When the user selects seats, the Booking Service calls the Movie Inventory Service to lock them.
POST /api/v1/shows/S101/seats/lock
{
  "userId": "U101",
  "seatIds": ["ST101", "ST103"],
  "lockDurationSeconds": 300
}
A simplified Spring Boot implementation in the Booking Service can look like:
public SeatLockResponse lockSeats(
        String showId,
        String userId,
        List seatIds) {

    return inventoryClient.lockSeats(
        showId,
        new LockSeatsRequest(userId, seatIds, 300)
    );
}
The Inventory Service receives the request and performs the actual database operation.

2. Inventory Service: Lock Transaction

The Inventory Service owns the SEAT table and performs the lock inside a database transaction. The Inventory Service uses a transactional service method:
@Transactional
public SeatLockResponse lockSeats(
        String showId,
        String userId,
        List seatIds) {

    List seats =
        seatRepository.findSeatsForUpdate(showId, seatIds);

    if (seats.size() != seatIds.size()) {
        throw new SeatNotFoundException();
    }

    boolean available = seats.stream()
        .allMatch(s -> s.getStatus() == SeatStatus.AVAILABLE);

    if (!available) {
        throw new SeatNotAvailableException();
    }

    String lockId = UUID.randomUUID().toString();
    Instant expiry = Instant.now().plusSeconds(300);

    seats.forEach(seat -> {
        seat.setStatus(SeatStatus.LOCKED);
        seat.setLockId(lockId);
        seat.setLockedBy(userId);
        seat.setLockExpiry(expiry);
    });

    seatRepository.saveAll(seats);

    return new SeatLockResponse(
        lockId,
        showId,
        seatIds,
        "LOCKED",
        expiry
    );
}
The repository uses SELECT ... FOR UPDATE so that concurrent booking requests cannot modify the same seats simultaneously.
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("""
    SELECT s
    FROM Seat s
    WHERE s.showId = :showId
      AND s.seatId IN :seatIds
""")
List<Seat> findSeatsForUpdate(
    @Param("showId") String showId,
    @Param("seatIds") List<String> seatIds
);
The resulting database operation is effectively:
BEGIN;

SELECT *
FROM SEAT
WHERE show_id = 'S101'
  AND seat_id IN ('ST101', 'ST103')
FOR UPDATE;

-- Verify all seats are AVAILABLE.

UPDATE SEAT
SET status = 'LOCKED',
    lock_id = 'L10001',
    locked_by = 'U101',
    lock_expiry = NOW() + INTERVAL '5 MINUTES'
WHERE show_id = 'S101'
  AND seat_id IN ('ST101', 'ST103');

COMMIT;
The transaction ends when the Inventory Service returns the response. The database locks are therefore held only for a very short period and are not held while the user completes payment.

3. Lock Response

If all requested seats are available, the Inventory Service returns:
{
  "lockId": "L10001",
  "showId": "S101",
  "seatIds": ["ST101", "ST103"],
  "status": "LOCKED",
  "expiresAt": "2026-08-02T17:15:00Z"
}
The Booking Service stores the lockId with the pending booking and starts the payment workflow.

The lockId acts as the reservation token that connects the lock operation with the later confirmation operation.

4. Payment

The Booking Service now calls the Payment Service. No database transaction remains open during payment.

If payment succeeds before the lock expires, the Booking Service proceeds with seat confirmation.

5. Confirm Seats

The Booking Service makes a second API call to the Inventory Service.
POST /api/v1/shows/S101/seats/confirm
{
  "lockId": "L10001",
  "userId": "U101",
  "seatIds": ["ST101", "ST103"]
}
This starts a new database transaction inside the Inventory Service.
@Transactional
public SeatConfirmResponse confirmSeats(
        String showId,
        String userId,
        String lockId,
        List<String> seatIds) {

    List<Seat> seats =
        seatRepository.findSeatsForUpdate(showId, seatIds);

    boolean valid = seats.size() == seatIds.size()
        && seats.stream().allMatch(seat ->
            seat.getStatus() == SeatStatus.LOCKED
            && seat.getLockId().equals(lockId)
            && seat.getLockedBy().equals(userId)
            && seat.getLockExpiry().isAfter(Instant.now())
        );

    if (!valid) {
        throw new SeatLockExpiredException();
    }

    seats.forEach(seat -> {
        seat.setStatus(SeatStatus.BOOKED);
        seat.setLockId(null);
        seat.setLockedBy(null);
        seat.setLockExpiry(null);
    });

    seatRepository.saveAll(seats);

    return new SeatConfirmResponse(
        lockId,
        showId,
        seatIds,
        "BOOKED"
    );
}
The database transaction is effectively:
BEGIN;

SELECT *
FROM SEAT
WHERE show_id = 'S101'
  AND seat_id IN ('ST101', 'ST103')
FOR UPDATE;

-- Verify lockId, userId and lock expiry.

UPDATE SEAT
SET status = 'BOOKED',
    lock_id = NULL,
    locked_by = NULL,
    lock_expiry = NULL
WHERE show_id = 'S101'
  AND seat_id IN ('ST101', 'ST103')
  AND status = 'LOCKED'
  AND lock_id = 'L10001'
  AND locked_by = 'U101'
  AND lock_expiry > NOW();

COMMIT;
The Inventory Service returns:
{
  "lockId": "L10001",
  "showId": "S101",
  "seatIds": ["ST101", "ST103"],
  "status": "BOOKED"
}
The Booking Service can now mark the booking as CONFIRMED.

6. Payment Failure or Lock Expiration

If payment fails or the user cancels the booking, the Booking Service asks the Inventory Service to release the seats.
POST /api/v1/shows/S101/seats/release
{
  "lockId": "L10001",
  "userId": "U101",
  "seatIds": ["ST101", "ST103"]
}
The Inventory Service verifies the lockId and ownership before releasing the seats.
UPDATE SEAT
SET status = 'AVAILABLE',
    lock_id = NULL,
    locked_by = NULL,
    lock_expiry = NULL
WHERE show_id = 'S101'
  AND seat_id IN ('ST101', 'ST103')
  AND status = 'LOCKED'
  AND lock_id = 'L10001'
  AND locked_by = 'U101';
Expired locks can also be released by a background cleanup process:
UPDATE SEAT
SET status = 'AVAILABLE',
    lock_id = NULL,
    locked_by = NULL,
    lock_expiry = NULL
WHERE status = 'LOCKED'
  AND lock_expiry <= NOW();

Non-Functional Requirements

1. Low Latency

Low latency is achieved by keeping frequently accessed movie, theatre, show, and seat information in Redis.

The Search Service uses Elasticsearch for fast movie and theatre searches, while the booking path performs only the database operations required for seat locking and confirmation.

Application services are stateless and can run multiple instances behind a Load Balancer.

2. High Availability

High availability is achieved by running multiple instances of each stateless service behind a Load Balancer.

If one instance fails, traffic is routed to another healthy instance. PostgreSQL uses replication and failover, while Redis and Kafka run as replicated clusters.

This removes single points of failure from the application and infrastructure layers.

3. Horizontal Scalability

The services are independently scalable. Additional instances of the Search Service can handle increasing search traffic, while additional Booking Service instances can handle booking requests.

Redis reduces database read load, and Kafka allows asynchronous workloads such as notifications, analytics, and reporting to scale independently.

4. Strong Consistency During Booking

Seat availability is maintained in the Movie Inventory Service, which acts as the source of truth for seat state. Seat locking is performed inside a short database transaction using SELECT ... FOR UPDATE.

The transaction verifies that all requested seats are available and changes them to LOCKED atomically.

Payment is performed outside the database transaction, and a second transaction confirms the seats after successful payment.

5. Fault Tolerance

The system isolates failures between services. Payment failures do not leave database transactions open because payment is performed after the seat-lock transaction completes.

If payment fails or a user cancels, the Booking Service releases the seats.

Kafka provides durable asynchronous processing for events, while retries and idempotent operations prevent transient failures from creating duplicate actions.

Conclusion

A movie ticket booking system must handle high read traffic, concurrent seat bookings, and reliable payment processing.

The Movie Inventory Service maintains movie, theatre, show, and seat inventory, while the Booking Service coordinates seat locking, payment, confirmation, and cancellation.

Atomic seat locking prevents double booking, while Redis improves read performance and Kafka enables asynchronous processing.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer β€’ Java β€’ Python β€’ Distributed Systems β€’ AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

πŸ’¬ Comments

Join the Discussion