Home/Learn/Low Level Design/Design a Movie Ticket Booking System

Design a Movie Ticket Booking System

Intermediate
LLD Interview Problems
Java Source Code

Model a cinema booking system with shows, seat types, concurrent seat locking, and a Singleton BookingSystem entry point.

Overview

A Movie Ticket Booking System allows users to browse Movies and Shows, select Seats, and confirm a Booking with Payment. Theatre contains Screens; each Screen hosts Shows at given times. Seat carries a SeatType (REGULAR, PREMIUM, RECLINER) and a SeatStatus (AVAILABLE, LOCKED, BOOKED). Locking prevents two users from booking the same seat concurrently — a short-lived lock is acquired before payment, then converted to BOOKED or released on timeout. PricingStrategy computes ticket price from SeatType. BookingSystem is a Singleton entry point. Observer fires booking confirmation events for email and push notifications.

Requirements Analysis

Functional: search movies by city/date, list shows for a movie, view seat map for a show, lock seats, confirm booking with payment, cancel booking. Non-functional: concurrent seat locking — only one user can lock a seat at a time, Singleton BookingSystem for global coordination, pluggable seat pricing.

Requirements
// Entities : BookingSystem, Movie, Theatre, Screen, Show, Seat, Booking, Payment
// Patterns : Singleton (BookingSystem), Strategy (seat pricing), Observer (booking confirmation)

Core Classes & Relationships

SeatType enum: REGULAR, PREMIUM, RECLINER. SeatStatus enum: AVAILABLE, LOCKED, BOOKED. Show links a Movie to a Screen with a start time and a seat map. Booking holds Show, List<Seat>, User, and payment status. SeatPricingStrategy interface has getPrice(SeatType). BookingConfirmationObserver fires on successful payment.

Java — enums & interfaces
public enum SeatType   { REGULAR, PREMIUM, RECLINER }
public enum SeatStatus { AVAILABLE, LOCKED, BOOKED }

public interface SeatPricingStrategy {
    double getPrice(SeatType type);
}

public class StandardSeatPricing implements SeatPricingStrategy {
    @Override public double getPrice(SeatType type) {
        return switch (type) {
            case REGULAR  -> 180.0;
            case PREMIUM  -> 280.0;
            case RECLINER -> 450.0;
        };
    }
}

public interface BookingConfirmationObserver {
    void onBookingConfirmed(Booking booking);
}

public class Show {
    private final String showId;
    private final Movie movie;
    private final Screen screen;
    private final LocalDateTime startTime;
    private final List<Seat> seats;

    public Show(String showId, Movie movie, Screen screen, LocalDateTime startTime, List<Seat> seats) {
        this.showId = showId; this.movie = movie; this.screen = screen;
        this.startTime = startTime; this.seats = seats;
    }
    public String getShowId()           { return showId; }
    public Movie getMovie()             { return movie; }
    public List<Seat> getSeats()        { return seats; }
    public LocalDateTime getStartTime() { return startTime; }
}

Java Implementation

Seat uses synchronized methods to safely lock and confirm status. BookingSystem.reserveSeats() iterates requested seats, locks each atomically, creates a Booking, and triggers payment. On payment success each seat becomes BOOKED; on failure all locks are released. Observers fire after successful confirmation.

Java — core classes
public class Seat {
    private final String seatId;
    private final String row;
    private final int number;
    private final SeatType type;
    private SeatStatus status = SeatStatus.AVAILABLE;

    public Seat(String seatId, String row, int number, SeatType type) {
        this.seatId = seatId; this.row = row; this.number = number; this.type = type;
    }
    public SeatType getType()   { return type; }
    public String getSeatId()   { return seatId; }

    public synchronized boolean lock() {
        if (status != SeatStatus.AVAILABLE) return false;
        status = SeatStatus.LOCKED;
        return true;
    }
    public synchronized void confirm()  { status = SeatStatus.BOOKED; }
    public synchronized void release()  { if (status == SeatStatus.LOCKED) status = SeatStatus.AVAILABLE; }
    public synchronized boolean isAvailable() { return status == SeatStatus.AVAILABLE; }
}

public class Booking {
    private final String bookingId;
    private final Show show;
    private final List<Seat> seats;
    private final String userId;
    private boolean paid = false;

    public Booking(String bookingId, Show show, List<Seat> seats, String userId) {
        this.bookingId = bookingId; this.show = show; this.seats = seats; this.userId = userId;
    }
    public void markPaid()          { this.paid = true; }
    public boolean isPaid()         { return paid; }
    public List<Seat> getSeats()    { return seats; }
    public Show getShow()           { return show; }
    public String getBookingId()    { return bookingId; }
    public String getUserId()       { return userId; }
}

public class BookingSystem {                              // Singleton
    private static volatile BookingSystem instance;
    private final SeatPricingStrategy pricing;
    private final List<BookingConfirmationObserver> observers = new ArrayList<>();
    private int counter = 0;

    private BookingSystem(SeatPricingStrategy pricing) { this.pricing = pricing; }

    public static BookingSystem getInstance(SeatPricingStrategy pricing) {
        if (instance == null) synchronized (BookingSystem.class) {
            if (instance == null) instance = new BookingSystem(pricing);
        }
        return instance;
    }

    public void addObserver(BookingConfirmationObserver o) { observers.add(o); }

    public Booking reserveSeats(Show show, List<String> seatIds, String userId) {
        List<Seat> targetSeats = show.getSeats().stream()
            .filter(s -> seatIds.contains(s.getSeatId()))
            .collect(Collectors.toList());

        List<Seat> locked = new ArrayList<>();
        for (Seat seat : targetSeats) {
            if (!seat.lock()) {
                locked.forEach(Seat::release);           // rollback all locks
                throw new IllegalStateException("Seat " + seat.getSeatId() + " not available");
            }
            locked.add(seat);
        }

        Booking booking = new Booking("BKG-" + (++counter), show, locked, userId);
        double total = locked.stream().mapToDouble(s -> pricing.getPrice(s.getType())).sum();
        System.out.printf("Booking %s created. Total: %.2f%n", booking.getBookingId(), total);

        // Simulate payment success
        booking.markPaid();
        locked.forEach(Seat::confirm);
        observers.forEach(o -> o.onBookingConfirmed(booking));
        return booking;
    }
}

Key Points to Remember

  • 1Synchronized seat.lock() prevents two threads from booking the same seat — rollback releases all acquired locks on partial failure.
  • 2Singleton BookingSystem ensures a single coordination point across all concurrent booking requests.
  • 3Strategy for seat pricing makes recliner pricing configurable without touching Booking or Show.
  • 4Observer fires confirmation events after payment so email/push services are fully decoupled from the booking transaction.

Interview Questions

Sign in to ask Aria
1

How would you implement a seat lock expiry so a user who abandons payment releases seats after 10 minutes?

HardBookMyShow
2

Why is Singleton appropriate for BookingSystem? What are the distributed-system trade-offs?

MediumAmazon
3

How would you scale this design to support millions of concurrent users on opening day of a blockbuster?

HardGoogle

Ask Aria about Design a Movie Ticket Booking System

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…