Skip to main content
Scenario Article

LLD for Movie Booking System: Designing BookMyShow

The Cinema Inventory Problem: What Makes It Hard

Abstract Algorithms•Mar 9, 2026
💡 Executive Summary

A Friday night show has 200 seats.

🎯 System Scenario & Requirements

System Goal

A Friday night show has 200 seats.

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • A Friday night show has 200 seats.
  • Two scenarios break a naive implementation: 1.
  • Double-booking: User A and User B click "Book Seat A1" at the same millisecond.

Constraints & Assumptions

  • Failure mode — skipping the LOCKED state: If code transitions directly from AVAILABLE to BOOKED in a single step, any failure mid-payment (timeout, gateway error, network drop) leaves the seat permanently BOOKED with no…
  • The LOCKED intermediate state isolates the inventory hold from the payment outcome, making reversal explicit, TTL-bounded, and safe.

⚡ Execution Stages & Decision Trace

1

Stage 1 — Key Entities in a Movie Booking System

Action / Decision:

Before writing a single line of concurrency code, you need a clear domain model.

Component Behavior:

A movie booking platform has six core entities: SeatType classifies seats by price tier: - PLATINUM — premium recliner seats (front/center) - GOLD — standard premium tier - SILVER — economy tier SeatStatus is the concur…

Observable Output: Every Booking references a Show, not the Movie directly — this is why "the 7 PM showing of a film" is completely separate inventory from "the 9 PM showing" of the same film.
Read Full Stage in Deep Dive ↓
📖

Deep Dive

Detailed Architectural Analysis

Scenario context

You run a cinema. A Friday night show has 200 seats. Two scenarios break a naive implementation:

  1. Double-booking: User A and User B click "Book Seat A1" at the same millisecond. Only one should succeed.
  2. Abandoned locks: User A locks Seat A1 but never completes payment. The seat should become available again after 10 minutes.

This needs a 3-state model + distributed lock strategy.


This walkthrough focuses on The Cinema Inventory Problem: What Makes It Hard.

Before making a design choice, clarify:

  • A Friday night show has 200 seats.
  • Two scenarios break a naive implementation: 1.
  • Double-booking: User A and User B click "Book Seat A1" at the same millisecond.

Concepts in play: low-level-design-systems, caching, api-gateway, distributed-locking

Stage 1 — Key Entities in a Movie Booking System

  • Before writing a single line of concurrency code, you need a clear domain model.
  • A movie booking platform has six core entities: SeatType classifies seats by price tier: - PLATINUM — premium recliner seats (front/center) - GOLD — standard premium tier - SILVER — economy tier SeatStatus is the concur…
  • Every Booking references a Show, not the Movie directly — this is why "the 7 PM showing of a film" is completely separate inventory from "the 9 PM showing" of the same film.
  • This hierarchy shapes every query, index, and cache partition in the real system.

Observable outcome: This hierarchy shapes every query, index, and cache partition in the real system.

Stage 2 — The Domain Hierarchy: City → Cinema → Screen → Show → Seat

  • The composition arrows ( -- ) running down the left spine — City → Cinema → Screen → Seat — model physical containment: a Screen cannot exist without its owning Cinema , and Seat instances are part of the Screen .
  • Show sits at the centre as the critical join entity, linking a Movie and a Screen at a specific startTime ; every Booking references a Show , not a Movie directly, which is why two screenings of the same film are comple…
  • The aggregation arrows ( o-- ) from BookingService to PricingStrategy and PaymentGateway express collaboration, not ownership — both dependencies are injected interfaces, making the service fully decoupled from any conc…
    • SeatType: GOLD , SILVER , PLATINUM — affects pricing.
classDiagram
    class City {+String name; +List~Cinema~ cinemas}
    class Cinema {+String name; +List~Screen~ screens}
    class Screen {+int id; +List~Seat~ seats}
    class Movie {+String title; +int durationMinutes}
    class Show {
        +Movie movie
        +Screen screen
        +LocalDateTime startTime
        +synchronized boolean bookSeats(List seats)
    }
    class Seat {
        -SeatStatus status
        +String row
        +int col
        +SeatType type
        +reserve(bookingId)
        +release()
        +confirm()
    }
    class PremiumSeat {
        +boolean hasRecline
        +boolean hasFootrest
    }
    class GroupSeat {
        +String groupReservationId
    }
    class Booking {
        +int id
        +Show show
        +List~Seat~ seats
        +PaymentStatus payment
    }
    class BookingService {
        +reserveSeats(User, Show, List~Seat~)
        +confirmBooking(Booking)
    }
    class PricingStrategy {
        <>
        +calculatePrice(Seat, Show, Customer) Money
    }
    class PaymentGateway {
        <>
        +charge(Customer, Money, String) PaymentResult
    }

    City *-- Cinema
    Cinema *-- Screen
    Screen *-- Seat
    Show "1" *-- "many" Seat : contains
    Show --> Screen
    Booking --> Show
    Booking --> Seat
    PremiumSeat --|> Seat : extends
    GroupSeat --|> Seat : extends
    BookingService o-- PricingStrategy : uses (injected)
    BookingService o-- PaymentGateway : uses (injected)

Observable outcome: - SeatType: GOLD , SILVER , PLATINUM — affects pricing.

Stage 3 — The 3-State Seat Model: Preventing Double Booking

  • The key insight: don't go directly from AVAILABLE to BOOKED.
  • Insert a temporary LOCKED state.
  • State machine: The booking flow: User B gets a graceful "This seat is currently being held by another user" message — not a race condition with undefined behavior.
stateDiagram-v2
    [*] --> AVAILABLE
    AVAILABLE --> LOCKED : User selects seat (10 min TTL)
    LOCKED --> BOOKED : Payment success
    LOCKED --> AVAILABLE : Payment timeout / failure
    BOOKED --> [*] : Show complete

Observable outcome: State machine: The booking flow: User B gets a graceful "This seat is currently being held by another user" message — not a race condition with undefined behavior.

Stage 4 — Booking Flow: From Seat Selection to Confirmation

  • Here is how a booking request moves through the system end-to-end — including both failure paths that keep inventory clean: The flowchart maps the complete booking transaction as a directed decision graph with two disti…
  • The first diamond — "All seats AVAILABLE?" — is the cheap early-exit: if any seat is already LOCKED or BOOKED by a concurrent user, the request is rejected before a lock is ever acquired on the current user's behalf, re…
  • Only after seats are successfully locked does control reach the payment gateway, whose failure branch explicitly releases the hold and returns inventory to AVAILABLE — this explicit release is what separates a well-desi…
  • Two distinct failure paths keep inventory accurate: 1.
flowchart TD
    A([User Selects Seats]) --> B{All seats AVAILABLE?}
    B -- No --> C([Error: Seat Already Held by Another User])
    B -- Yes --> D[Lock Seats  10 min TTL applied]
    D --> E([User Redirected to Payment Page])
    E --> F{Payment Successful?}
    F -- Timeout or Failure --> G[Release Lock  Seats set back to AVAILABLE]
    F -- Yes --> H[Set Seats to BOOKED]
    H --> I([Confirmation Email Sent to User])
    G --> J([Error: Payment Failed  Seats Released])

Observable outcome: Two distinct failure paths keep inventory accurate: 1.

Stage 5 — Deep Dive: Synchronized Booking Implementation

  • synchronized on the Show method ensures that the check-and-lock is atomic — no two threads can interleave between the check ( AVAILABLE ) and the lock ( LOCKED ).
  • 📊 Reserve Seats: Booking Sequence The sequence diagram traces the critical path for seat reservation — from the initial lock request (preventing other threads from booking the same seat) through payment confirmation to…
  • The synchronized annotation on the BS- SH call marks the boundary of the atomic monitor: every arrow that follows — the status check ( SH- S: status == AVAILABLE?
  • ) and the lock ( SH- S: reserve(bookingId) ) — executes inside a single thread-held critical section, making interleaving between the check and the commit impossible.
public class Show {
    private final int id;
    private final List<Seat> seats;

    public synchronized BookingResult reserveSeats(User user, List<Integer> seatIds) {
        // Phase 1: Check all requested seats are available
        List<Seat> selected = new ArrayList<>();
        for (int id : seatIds) {
            Seat s = findSeat(id);
            if (s.getStatus() != SeatStatus.AVAILABLE) {
                return BookingResult.failure("Seat " + id + " is not available");
            }
            selected.add(s);
        }
        // Phase 2: Lock all seats together (atomic within synchronized block)
        Instant lockExpiry = Instant.now().plusSeconds(600);
        for (Seat s : selected) {
            s.setStatus(SeatStatus.LOCKED);
            s.setLockExpiry(lockExpiry);
        }
        return BookingResult.success(new Booking(user, this, selected));
    }
}
sequenceDiagram
    participant U as User
    participant BS as BookingService
    participant SH as Show
    participant S as Seat
    participant PG as PaymentGateway
    U->>BS: reserveSeats(user, show, seatIds)
    BS->>SH: reserveSeats(user, seatIds) synchronized
    SH->>S: status == AVAILABLE?
    S-->>SH: true
    SH->>S: reserve(bookingId)  LOCKED
    SH-->>BS: BookingResult.success(booking)
    BS->>PG: charge(user, amount, bookingId)
    PG-->>BS: PaymentResult.success
    BS->>S: confirm()  BOOKED
    BS-->>U: Booking{id, seats, CONFIRMED}

Observable outcome: ) and the lock ( SH- S: reserve(bookingId) ) — executes inside a single thread-held critical section, making interleaving between the check and the commit impossible.

Stage 6 — Pricing Strategy Pattern

  • Different shows and seat types need different prices: 📊 Enhanced Booking Domain Class Diagram This enhanced diagram isolates the Seat subsystem with its full type hierarchy and wires in the three Strategy/Gateway inter…
  • The enumeration classes SeatType and SeatStatus appear as owned associations of Seat (solid arrows from Seat ), making explicit that price tier and booking state are intrinsic to the seat itself — not to the Booking or…
  • The dashed implementation arrows ( ..
  • ) from HourlyPricing and DynamicPricing to PricingStrategy are the Strategy pattern in UML form: both are interchangeable at the BookingService injection site, so the active pricing rule switches at Spring configuration…
public interface PricingStrategy {
    Money calculatePrice(Seat seat, Show show, Customer customer);
}

public class HourlyPricing implements PricingStrategy {
    public Money calculatePrice(Seat seat, Show show, Customer customer) {
        double base = seat.getType() == SeatType.GOLD ? 15.0 : 10.0;
        double multiplier = show.isWeekend() ? 1.2 : 1.0;  // weekend surcharge
        return Money.of(base * multiplier);
    }
}

public class DynamicPricing implements PricingStrategy {
    public Money calculatePrice(Seat seat, Show show, Customer customer) {
        double occupancy = show.getBookedPercent();
        return Money.of(BASE_PRICE * (1 + occupancy));  // price rises with demand
    }
}
classDiagram
    class Seat {
        -SeatStatus status
        +String row
        +int col
        +SeatType type
        +reserve(String bookingId) void
        +release() void
        +confirm() void
    }
    class PremiumSeat {
        +boolean hasRecline
        +boolean hasFootrest
    }
    class GroupSeat {
        +String groupReservationId
    }
    class SeatType {
        <>
        PLATINUM
        GOLD
        SILVER
    }
    class SeatStatus {
        <>
        AVAILABLE
        LOCKED
        BOOKED
    }
    class PricingStrategy {
        <>
        +calculatePrice(Seat, Show, Customer) Money
    }
    class HourlyPricing {
        +calculatePrice(Seat, Show, Customer) Money
    }
    class DynamicPricing {
        +calculatePrice(Seat, Show, Customer) Money
    }
    class SeatSelector {
        <>
        +findAvailable(Show, SeatType, int) List~Seat~
    }
    class PaymentGateway {
        <>
        +charge(Customer, Money, String) PaymentResult
    }
    class BookingService {
        -PricingStrategy pricer
        -SeatSelector selector
        -PaymentGateway gateway
        +reserveSeats(User, Show, List~Seat~) Booking
        +confirmBooking(Booking) void
    }
    PremiumSeat --|> Seat : extends
    GroupSeat --|> Seat : extends
    Seat --> SeatStatus : state
    Seat --> SeatType : type
    HourlyPricing ..|> PricingStrategy : implements
    DynamicPricing ..|> PricingStrategy : implements
    BookingService o-- PricingStrategy : uses (injected)
    BookingService o-- SeatSelector : uses (injected)
    BookingService o-- PaymentGateway : uses (injected)

Observable outcome: ) from HourlyPricing and DynamicPricing to PricingStrategy are the Strategy pattern in UML form: both are interchangeable at the BookingService injection site, so the active pricing rule switches at Spring configuration…

End-to-end outcome

A Friday night show has 200 seats.

Alternatives considered

OOP Decision Guide: Patterns and Extension Points

Start with synchronized on Seat methods and constructor-injected strategies.

Failure and degraded modes

OOP Design Trade-offs and Failure Modes in the Booking System

  • Detection: Failure mode — skipping the LOCKED state: If code transitions directly from AVAILABLE to BOOKED in a single step, any failure mid-payment (timeout, gateway error, network drop) leaves the seat perman…
  • User or operator impact: Failure mode — skipping the LOCKED state: If code transitions directly from AVAILABLE to BOOKED in a single step, any failure mid-payment (timeout, gateway error, network drop) leaves the seat perman…

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit