All Posts

LLD for Movie Booking System: Designing BookMyShow

Designing a movie booking system involves handling concurrency (no double bookings!), managing complex hierarchies, and ensuring a smooth user experie

Abstract AlgorithmsAbstract Algorithms
ยทยท5 min read
Share
Share on X / Twitter
Share on LinkedIn
Copy link

TLDR: A Movie Booking System (like BookMyShow) is an inventory management problem with an expiry: seats expire when the show starts. The core engineering challenge is preventing double-booking under concurrent user load with a 3-state seat model (AVAILABLE โ†’ LOCKED โ†’ BOOKED).


๐Ÿ“– The Cinema Inventory Problem: What Makes It Hard

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.


๐Ÿ”ข The Domain Hierarchy: City โ†’ Cinema โ†’ Screen โ†’ Show โ†’ Seat

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 {
        +String row
        +int col
        +SeatType type
        +SeatStatus status
    }
    class Booking {
        +int id
        +Show show
        +List~Seat~ seats
        +PaymentStatus payment
    }

    City *-- Cinema
    Cinema *-- Screen
    Screen *-- Seat
    Show --> Screen
    Booking --> Show
    Booking --> Seat
  • SeatType: GOLD, SILVER, PLATINUM โ€” affects pricing.
  • SeatStatus: AVAILABLE, LOCKED, BOOKED โ€” the concurrency core.
  • Show is the intersection of Movie + Screen + StartTime.

โš™๏ธ 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:

stateDiagram-v2
    [*] --> AVAILABLE
    AVAILABLE --> LOCKED : User selects seat (10 min TTL)
    LOCKED --> BOOKED : Payment success
    LOCKED --> AVAILABLE : Payment timeout / failure
    BOOKED --> [*] : Show complete

The booking flow:

TimeUser AUser BSeat A1 Status
10:00:00Selects A1โ€”AVAILABLE
10:00:01System locks A1โ€”LOCKED (expires 10:10)
10:00:02Directed to paymentSelects A1โ€”
10:00:02โ€”System: seat unavailableLOCKED (no change)
10:05:00Payment successโ€”BOOKED

User B gets a graceful "This seat is currently being held by another user" message โ€” not a race condition with undefined behavior.


๐Ÿง  Java Implementation: Synchronized Booking

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));
    }
}

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).


โš™๏ธ Pricing Strategy Pattern

Different shows and seat types need different prices:

public interface PricingStrategy {
    double calculatePrice(Show show, Seat seat);
}

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

public class DynamicPricing implements PricingStrategy {
    public double calculatePrice(Show show, Seat seat) {
        double occupancy = show.getBookedPercent();
        return BASE_PRICE * (1 + occupancy);  // price rises with demand
    }
}

โš–๏ธ Scaling the Booking System

The synchronized approach works for a single JVM. At scale:

ScaleApproach
Single serversynchronized on ShowO(1) per booking; blocks concurrent requests to same show
Multi-serverDistributed lock (Redis SETNX with TTL)Lock key = show:{id}:seat:{id}
High scaleOptimistic locking with DB CAS (UPDATE seat SET status='LOCKED' WHERE status='AVAILABLE' AND id=?)No lock held between check and update
Seat expiryScheduled job (every minute) reverts LOCKED โ†’ AVAILABLE where lock_expiry < NOW()Clears abandoned holds

๐Ÿ“Œ Summary

  • 3-state seat model (AVAILABLE โ†’ LOCKED โ†’ BOOKED) prevents double-booking and handles abandoned checkouts.
  • synchronized bookSeats() makes the check-and-lock atomic; no two users can hold the same seat.
  • Strategy Pattern for pricing decouples business rules from booking logic.
  • Distributed scale moves from JVM synchronized to Redis SETNX or DB CAS.

๐Ÿ“ Practice Quiz

  1. Why is a 3-state model (AVAILABLE โ†’ LOCKED โ†’ BOOKED) better than a 2-state model (AVAILABLE โ†’ BOOKED)?

    • A) It reduces database writes.
    • B) The LOCKED state holds the seat for the user during payment without permanently booking it, allowing auto-release on timeout.
    • C) It's required by cinema software standards.
      Answer: B
  2. In the single-server Java implementation, what prevents two threads from both seeing a seat as AVAILABLE and both locking it?

    • A) volatile on the seat status field.
    • B) synchronized on bookSeats() โ€” only one thread executes the check-and-lock block at a time.
    • C) A database constraint.
      Answer: B
  3. At multi-server scale, which mechanism replaces Java's synchronized keyword for preventing double booking?

    • A) Thread pools with fixed queue size.
    • B) Distributed lock via Redis SETNX (or DB optimistic locking with CAS).
    • C) Read replicas for the seat table.
      Answer: B

Abstract Algorithms

Written by

Abstract Algorithms

@abstractalgorithms

More Posts

System Design Service Discovery and Health Checks: Routing Traffic to Healthy Instances

TLDR: Service discovery is how clients find the right service instance at runtime, and health checks are how systems decide whether an instance should receive traffic. Together, they turn dynamic infrastructure from guesswork into deterministic routi...

โ€ข8 min read

System Design Roadmap: A Complete Learning Path from Basics to Advanced Architecture

TLDR: This roadmap organizes every system-design-tagged post in this repository into learning groups and a recommended order. It is designed for interview prep and practical architecture thinking, from fundamentals to scaling, reliability, and implem...

โ€ข10 min read

System Design Observability, SLOs, and Incident Response: Operating Systems You Can Trust

TLDR: Observability is how you understand system behavior from telemetry, SLOs are explicit reliability targets, and incident response is the execution model when those targets are at risk. Together, they convert operational chaos into measurable, re...

โ€ข8 min read

System Design Message Queues and Event-Driven Architecture: Building Reliable Asynchronous Systems

TLDR: Message queues and event-driven architecture let services communicate asynchronously, absorb bursty traffic, and isolate failures. The core design challenge is not adding a queue. It is defining delivery semantics, retry behavior, and idempoten...

โ€ข8 min read