Back to LLD explorer
Concurrency / Transactional• Patterns: Observer, Strategy, State

BookMyShow (Movie Ticket Booking)

Hard

Problem Summary

Design a high-concurrency movie ticket reservation system managing multi-screen cinema seats, temporary 10-minute checkout locks, dynamic weekend pricing, and distributed locking.

Functional Scope

  • Real-time seat layout map (Available, Locked, Booked, Out of Service).
  • Temporary 10-minute seat locking during user payment checkout.
  • Automatic unlock daemon resetting seats if payment times out.
  • Pluggable dynamic pricing strategy (Weekend surge, Holiday premium, Row category).
  • Zero double-booking guarantee under concurrent checkout attempts.

Entity-Relationship (ER) Schema

Cinema [1] --> [*] Screen
Screen [1] --> [*] Show
Show [1] --> [*] ShowSeat
ShowSeat [1] --> [1] SeatStatus
Show [1] --> [1] PricingStrategy

Design Approach

Apply a two-phase seat locking protocol with Redis TTL or in-memory timestamps to hold seats for 10 minutes. Use optimistic concurrency or row-level database locks to eliminate race conditions during high-demand movie premieres.

Core Classes & Models

CinemaService (Orchestrator managing show listings and seat locks)Show (Movie, Screen, ShowTime, SeatMap)ShowSeat (SeatNo, SeatCategory: SILVER, GOLD, PLATINUM, Status: AVAILABLE, LOCKED, BOOKED)PricingStrategy (WeekendPricing, PeakHourPricing)SeatLockManager (Distributed Redis/memory lock manager with TTL)
Code Blueprint
102 lines
/**
 * Low-Level Design: BookMyShow Cinema Ticketing System
 * Design Patterns Applied:
 *  - State Pattern: Seat states (AvailableState, HeldState, BookedState)
 *  - Strategy Pattern: Dynamic seat pricing (Normal, VIP, Weekend Surge)
 *  - Observer Pattern: Temporary hold expiry timer and seat release notifications
 */

import java.util.*;

enum SeatType { SILVER, GOLD, PLATINUM }
enum SeatStatus { AVAILABLE, HELD, BOOKED }

// --- [DESIGN PATTERN: Strategy] Seat pricing strategy ---
interface SeatPricingStrategy {
    double getPrice(SeatType type, boolean isWeekend);
}

class StandardPricingStrategy implements SeatPricingStrategy {
    @Override
    public double getPrice(SeatType type, boolean isWeekend) {
        double base = switch (type) {
            case SILVER -> 10.0;
            case GOLD -> 15.0;
            case PLATINUM -> 25.0;
        };
        return isWeekend ? base * 1.25 : base;
    }
}

// --- [DESIGN PATTERN: Observer] Seat release notifier ---
interface SeatHoldObserver {
    void onSeatReleased(String seatNumber);
}

class Seat {
    private final String seatNumber;
    private final SeatType type;
    private SeatStatus status = SeatStatus.AVAILABLE;

    public Seat(String seatNumber, SeatType type) {
        this.seatNumber = seatNumber;
        this.type = type;
    }
    public String getSeatNumber() { return seatNumber; }
    public SeatType getType() { return type; }
    public SeatStatus getStatus() { return status; }
    public void setStatus(SeatStatus status) { this.status = status; }
}

class ShowBookingService {
    private final Map<String, Seat> seats = new HashMap<>();
    private final SeatPricingStrategy pricingStrategy;
    private final List<SeatHoldObserver> observers = new ArrayList<>();

    public ShowBookingService(SeatPricingStrategy strategy) {
        this.pricingStrategy = strategy;
    }

    public void addSeat(Seat seat) { seats.put(seat.getSeatNumber(), seat); }
    public void addObserver(SeatHoldObserver o) { observers.add(o); }

    public synchronized boolean holdSeats(List<String> seatNumbers, String userId) {
        for (String sn : seatNumbers) {
            Seat s = seats.get(sn);
            if (s == null || s.getStatus() != SeatStatus.AVAILABLE) {
                System.out.println("❌ Seat " + sn + " is already reserved or booked!");
                return false;
            }
        }
        for (String sn : seatNumbers) {
            seats.get(sn).setStatus(SeatStatus.HELD);
        }
        System.out.println("⏳ Held seats " + seatNumbers + " for user " + userId + " (10m TTL lock)");
        return true;
    }

    public synchronized boolean confirmBooking(List<String> seatNumbers) {
        double total = 0.0;
        for (String sn : seatNumbers) {
            Seat s = seats.get(sn);
            if (s.getStatus() != SeatStatus.HELD) return false;
            s.setStatus(SeatStatus.BOOKED);
            total += pricingStrategy.getPrice(s.getType(), false);
        }
        System.out.printf("🎟️ Booking CONFIRMED for seats %s! Total: $%.2f%n", seatNumbers, total);
        return true;
    }
}

public class Main {
    public static void main(String[] args) {
        ShowBookingService bookingService = new ShowBookingService(new StandardPricingStrategy());
        bookingService.addSeat(new Seat("A1", SeatType.PLATINUM));
        bookingService.addSeat(new Seat("A2", SeatType.PLATINUM));
        bookingService.addSeat(new Seat("B1", SeatType.GOLD));

        System.out.println("--- BookMyShow Reservation Flow ---");
        bookingService.holdSeats(List.of("A1", "A2"), "USR-401");
        bookingService.confirmBooking(List.of("A1", "A2"));
    }
}