Back to LLD Problems: Parking Lot, Elevator, Chess, etc.
Q7Question 7 of 12

Design a Movie Ticket Booking System (like BookMyShow)

Key Concepts
  • A Show is one screening of a Movie on a Screen at a time, tracking each seat as AVAILABLE, LOCKED, or BOOKED.
  • The two-phase approach (lock then confirm) prevents double-selling: seats are locked with a short expiry while the user pays.
  • If payment succeeds within the window, seats become BOOKED; if the window expires, a background sweeper returns them to AVAILABLE.
  • The lock must be atomic: checking and setting seat state happens under a lock or compare-and-set to prevent two users both locking the same seat.
  • In distributed deployments, a Redis lock keyed by show and seat with a TTL provides both atomicity and automatic expiry.
  • The TTL on the lock is not optional detail but core correctness: without it, abandoned carts permanently freeze inventory.
  • This is the same discipline used at National Bank of India when many clients contend for a single limited resource.

Definition

A movie booking system sells seats for shows with short-lived seat locks that prevent double-selling under heavy concurrency.
  • Movie – title, genre, duration.
  • Cinema – a venue with multiple Screen objects.
  • Screen – has a screen number and a fixed set of Seat positions.
  • Show – one screening of a Movie on a Screen at a time; holds the state of each seat (AVAILABLE, LOCKED, BOOKED).
  • Booking – created when payment succeeds; references the Show and the booked seats.
  • Seat locking – a two-phase mechanism (lock with TTL, then confirm) that prevents double-selling under heavy concurrency.

Booking a cinema seat is like reserving a specific numbered locker that thousands want at the same second. When Rajesh clicks seat A5 for the 9pm show, National Bank of India style systems place a short hold (lock) on A5 while he pays, so Priya cannot grab the same seat. If he does not pay in time, the hold expires and A5 is free again.

Understanding the Concepts

What are the core entities?

The core entities are Movie, Cinema, Screen, Seat, Show, SeatStatus, and Booking.

JAVA
package com.bank.booking.model;

public enum SeatStatus {
    AVAILABLE, LOCKED, BOOKED
}
JAVA
package com.bank.booking.model;

public class Seat {
    private final String seatId;
    private SeatStatus status;
    private String lockedByUserId;
    private long lockExpiry;

    public Seat(String seatId) {
        this.seatId = seatId;
        this.status = SeatStatus.AVAILABLE;
    }

    public synchronized boolean lock(String userId, long ttlMillis) {
        if (status != SeatStatus.AVAILABLE) return false;
        this.status = SeatStatus.LOCKED;
        this.lockedByUserId = userId;
        this.lockExpiry = System.currentTimeMillis() + ttlMillis;
        return true;
    }

    public synchronized boolean confirm(String userId) {
        if (status != SeatStatus.LOCKED
                || !userId.equals(lockedByUserId)) return false;
        this.status = SeatStatus.BOOKED;
        return true;
    }

    public synchronized void releaseIfExpired() {
        if (status == SeatStatus.LOCKED
                && System.currentTimeMillis() > lockExpiry) {
            this.status = SeatStatus.AVAILABLE;
            this.lockedByUserId = null;
            System.out.println("Seat " + seatId
                    + " lock expired, released");
        }
    }

    public String getSeatId() { return seatId; }
    public SeatStatus getStatus() { return status; }
}

How does the two-phase lock-then-confirm work?

This is the central mechanism that every real booking platform uses.

DIAGRAM
1. User selects seats → seats transition to LOCKED with a short TTL (e.g., 5 minutes).
2. User completes payment within the TTL → seats transition to BOOKED.
3. If the TTL expires → a background sweeper returns seats to AVAILABLE.
JAVA
package com.bank.booking.service;

import com.bank.booking.model.*;
import java.util.List;

public class BookingService {
    private static final long LOCK_TTL = 5 * 60 * 1000; // 5 minutes

    public boolean lockSeats(Show show, List<String> seatIds,
                              String userId) {
        for (String seatId : seatIds) {
            Seat seat = show.getSeat(seatId);
            if (!seat.lock(userId, LOCK_TTL)) {
                // Rollback any already-locked seats
                seatIds.stream()
                        .map(show::getSeat)
                        .filter(s -> s.getStatus() == SeatStatus.LOCKED)
                        .forEach(Seat::releaseIfExpired);
                return false;
            }
        }
        System.out.println("Locked seats " + seatIds
                + " for user " + userId);
        return true;
    }

    public Booking confirmBooking(Show show, List<String> seatIds,
                                   String userId) {
        for (String seatId : seatIds) {
            if (!show.getSeat(seatId).confirm(userId)) {
                throw new IllegalStateException(
                        "Cannot confirm seat " + seatId);
            }
        }
        Booking booking = new Booking(
                java.util.UUID.randomUUID().toString(),
                show, seatIds, userId);
        System.out.println("Booking confirmed: " + booking.getBookingId());
        return booking;
    }
}

Why must the lock be atomic?

Checking that a seat is available and setting it to locked must happen as one indivisible step. Otherwise two users both see AVAILABLE and both lock the same seat.

JAVA
// The lock method is synchronized on the Seat object
public synchronized boolean lock(String userId, long ttlMillis) {
    if (status != SeatStatus.AVAILABLE) return false;
    this.status = SeatStatus.LOCKED;
    // ...
}

In a distributed deployment, use a Redis lock keyed by show and seat with a TTL:

TEXT
SET show:S1:seat:A5 userId NX PX 300000

The NX flag ensures the key is set only if it does not already exist (atomic), and PX 300000 sets a 5-minute expiry.

How does the background sweeper work?

A scheduled task periodically scans locked seats and releases any whose TTL has expired.

JAVA
package com.bank.booking.service;

import com.bank.booking.model.Show;

public class LockSweeper implements Runnable {
    private final Show show;

    public LockSweeper(Show show) {
        this.show = show;
    }

    @Override
    public void run() {
        show.getAllSeats().forEach(
                seat -> seat.releaseIfExpired());
    }
}

How does payment and cancellation work?

Payment goes through a payment strategy. Cancellation releases seats back to AVAILABLE.

JAVA
public void cancel(Booking booking, Show show) {
    for (String seatId : booking.getSeatIds()) {
        show.getSeat(seatId).release();
    }
    System.out.println("Booking " + booking.getBookingId()
            + " cancelled, seats released");
}

What about the Show entity?

A Show is one screening of a Movie on a Screen at a specific time. It holds the state of each seat.

JAVA
package com.bank.booking.model;

import java.time.LocalDateTime;
import java.util.*;

public class Show {
    private final String showId;
    private final Movie movie;
    private final Screen screen;
    private final LocalDateTime showTime;
    private final Map<String, Seat> seats;

    public Show(String showId, Movie movie, Screen screen,
                LocalDateTime showTime, List<String> seatIds) {
        this.showId = showId;
        this.movie = movie;
        this.screen = screen;
        this.showTime = showTime;
        this.seats = new LinkedHashMap<>();
        for (String id : seatIds) {
            seats.put(id, new Seat(id));
        }
    }

    public Seat getSeat(String seatId) { return seats.get(seatId); }
    public Collection<Seat> getAllSeats() { return seats.values(); }
}

What should a senior engineer know?

  • The TTL on the lock is core correctness, not optional. Without it, abandoned selections permanently starve inventory.
  • In a distributed system, Redis with NX + PX gives both atomicity and automatic expiry.
  • The lock-then-confirm pattern is the same two-phase approach used in banking for hold-then-capture on credit cards.
  • For popular shows, thousands of users contend for the same seats simultaneously, so the locking mechanism must be highly efficient.

What are common interview follow-up questions?

  • How do you handle partial lock failure (some seats lock, one fails)?
  • What happens if payment takes longer than the TTL?
  • How would you implement seat selection UI showing real-time availability?
  • How do you handle refunds on cancellation?
  • How would you scale this to millions of concurrent users?

Visual Overview

DIAGRAM
  Two-Phase Lock-Then-Confirm:
  ────────────────────────────

  User selects A5, A6
       │
       ▼
  lockSeats([A5, A6], userId, TTL=5min)
       │
       ▼
  Seats: AVAILABLE → LOCKED (5 min TTL)
       │
       ├── Payment succeeds within TTL
       │       ▼
       │   confirmBooking()
       │   Seats: LOCKED → BOOKED
       │
       └── TTL expires (no payment)
               ▼
           LockSweeper runs
           Seats: LOCKED → AVAILABLE
DIAGRAM
  Seat State Transitions:
  ───────────────────────

  AVAILABLE ──lock──→ LOCKED ──confirm──→ BOOKED
       ▲                 │
       │            TTL expires
       └─── release ─────┘

Key Takeaway

When you design a movie ticket booking system, treat concurrency as the first-class problem, because the entire difficulty is making sure a single seat is never sold to two people during a blockbuster rush. A Show is one screening of a Movie on a Screen at a time, and it tracks each seat as available, locked, or booked.

The two-phase approach that every real platform uses is lock then confirm. When a user selects seats, atomically move them from available to locked with a short expiry of a few minutes. If payment succeeds within that window, the seats become booked. If the window expires, a background sweeper returns the seats to available so an abandoned cart never freezes inventory.

The atomicity of the lock is everything. Checking that a seat is available and setting it to locked must happen as one indivisible step, otherwise two users both see available and both lock the same seat. In a single node, use a synchronised method. In a distributed deployment, use a Redis lock keyed by show and seat with a TTL, which gives both atomicity and automatic expiry.

The remaining pieces are straightforward: payment through a payment strategy, cancellation that releases seats back to available, and the show entity that ties a movie, screen, and time together. The senior insight is that the TTL on the lock is not optional detail but core correctness, because without it abandoned selections permanently starve inventory. At National Bank of India, this is the same discipline used when many clients contend for a single limited resource.

Remember one sentence: a movie booking system uses a two-phase lock-then-confirm with a TTL to atomically sell seats without double-booking under heavy concurrency.

---

Share
Use ← → arrow keys to navigate