LLD CASE STUDIES / OBJECT DESIGN BRIEF

Design a movie ticket system

Design a movie ticket booking system like BookMyShow.

AdvancedPhase 09 / Topic 8 of 10ResponsibilitiesCollaborationsExtensibility
01

Overview

Design a movie ticket booking system like BookMyShow. Requirements: cities have cinemas with screens; screens have seat layouts (regular, premium); movies have shows at times on screens; users search shows, view seat availability, select seats, hold them for a few minutes while paying, and receive a booking; failed or abandoned payments release seats; cancellations follow a refund policy.

The heart of the problem is concurrency: two users must never book the same seat. The standard design uses a seat hold (lock) with an expiry per show seat, acquired atomically for all selected seats, confirmed on successful payment, and released on failure or timeout. Pricing is a strategy (seat type, time of day, weekend), and payments use an adapter.

Putting items in a shop's reserved basket

When you pick seats, the cashier puts a 'reserved for 10 minutes' tag on them. Nobody else can take them while you pay. If you walk away, the tags expire and the seats return to the shelf.

02

When to use it

  • Top LLD interview problem combining modeling and concurrency.
  • Any reservation system with limited inventory (flights, hotels, events).
  • Practicing atomic holds with expiry.
03

Where it shows up in interviews

Inventory holds

Recognize it when: limited items, payment takes time.

  • Design a movie ticket system
  • Design a flight booking system
  • Design a concert ticketing system
Concurrent booking

Recognize it when: many users compete for the same seats.

  • Design BookMyShow
  • Design a hotel reservation system
04

Where it is used in real software

BookMyShow and Fandango

Hold selected seats for a few minutes during checkout.

Ticketmaster

Uses virtual queues and holds to handle huge demand spikes.

Airline inventory

Seat holds and fare classes follow similar patterns in reservation systems.

05

Key terms

Show
A movie on a screen at a time.
ShowSeat
A seat for a specific show with status (available, held, booked).
Seat hold
Temporary exclusive lock with expiry.
Booking
Confirmed seats with payment reference.
Pricing strategy
Computes price by seat type and show time.
06

How it works, step by step

  1. 1
    Clarify

    Search scope, seat types, hold duration, payment, cancellation.

  2. 2
    Entities

    City, Cinema, Screen, Seat, Movie, Show, ShowSeat, Hold, Booking, Payment.

  3. 3
    Hold seats atomically

    All requested seats or none; expiry timestamp.

  4. 4
    Confirm or release

    Payment success books; failure or timeout releases.

  5. 5
    Cancellation

    Refund policy strategy, seats become available.

Two users race for seats A5 and A6
Step 1 / 4
User 1
SeatLock
User 2
Payment
Booking

STEP 1User 1 holds A5 and A6 atomically, expiring in 10 minutes.

07

Seat state transitions

ShowSeat lifecycle

Step 1 / 4
FromEventToCondition
AVAILABLEhold(user)HELDAll requested seats available
HELDconfirm(user, payment ok)BOOKEDHold not expired and owned by user
HELDexpire / payment failedAVAILABLE-
BOOKEDcancel(user)AVAILABLERefund per policy

NOWFrom: AVAILABLE | Event: hold(user) | To: HELD | Condition: All requested seats available

Holds make the slow payment step safe without locking seats forever.

08

Implementation

import java.time.*;import java.util.*; enum SeatType { REGULAR, PREMIUM }record Seat(String id, SeatType type) {}record Show(String id, String movie, LocalDateTime start, List<Seat> seats) {}record Booking(String id, String showId, String userId, List<String> seatIds, long totalCents) {} interface PricingStrategy { long price(Show show, Seat seat); }final class StandardPricing implements PricingStrategy {    public long price(Show show, Seat seat) {        long base = seat.type() == SeatType.PREMIUM ? 1500 : 1000;        DayOfWeek d = show.start().getDayOfWeek();        return (d == DayOfWeek.SATURDAY || d == DayOfWeek.SUNDAY) ? base * 12 / 10 : base;    }} interface PaymentGateway { boolean charge(String userId, long cents); } final class SeatLockManager {    private record Hold(String userId, Instant expiresAt) {}    private final Map<String, Hold> holds = new HashMap<>();        // key: showId:seatId    private final Set<String> booked = new HashSet<>();    private final Clock clock;    private final Duration ttl;     SeatLockManager(Clock clock, Duration ttl) { this.clock = clock; this.ttl = ttl; }     synchronized boolean hold(String showId, List<String> seatIds, String userId) {        Instant now = clock.instant();        for (String s : seatIds) {                                   // check all first: all-or-nothing            String k = showId + ":" + s;            Hold h = holds.get(k);            boolean heldByOther = h != null && h.expiresAt().isAfter(now) && !h.userId().equals(userId);            if (booked.contains(k) || heldByOther) return false;        }        seatIds.forEach(s -> holds.put(showId + ":" + s, new Hold(userId, now.plus(ttl))));        return true;    }     synchronized boolean confirm(String showId, List<String> seatIds, String userId) {        Instant now = clock.instant();        for (String s : seatIds) {            Hold h = holds.get(showId + ":" + s);            if (h == null || !h.userId().equals(userId) || !h.expiresAt().isAfter(now)) return false;        }        seatIds.forEach(s -> { holds.remove(showId + ":" + s); booked.add(showId + ":" + s); });        return true;    }     synchronized void release(String showId, List<String> seatIds, String userId) {        seatIds.forEach(s -> holds.computeIfPresent(showId + ":" + s, (k, h) -> h.userId().equals(userId) ? null : h));    }     synchronized boolean isAvailable(String showId, String seatId) {        String k = showId + ":" + seatId;        Hold h = holds.get(k);        return !booked.contains(k) && (h == null || !h.expiresAt().isAfter(clock.instant()));    }} final class BookingService {    private final Map<String, Show> shows = new HashMap<>();    private final SeatLockManager locks;    private final PricingStrategy pricing;    private final PaymentGateway payments;     BookingService(SeatLockManager locks, PricingStrategy pricing, PaymentGateway payments) { this.locks = locks; this.pricing = pricing; this.payments = payments; }    void addShow(Show s) { shows.put(s.id(), s); }     List<Seat> availableSeats(String showId) {        return shows.get(showId).seats().stream().filter(s -> locks.isAvailable(showId, s.id())).toList();    }     Booking book(String userId, String showId, List<String> seatIds) {        Show show = Optional.ofNullable(shows.get(showId)).orElseThrow(() -> new IllegalArgumentException("Unknown show"));        Map<String, Seat> byId = new HashMap<>();        show.seats().forEach(s -> byId.put(s.id(), s));        if (!byId.keySet().containsAll(seatIds)) throw new IllegalArgumentException("Unknown seat");         if (!locks.hold(showId, seatIds, userId)) throw new IllegalStateException("One or more seats unavailable");        long total = seatIds.stream().mapToLong(id -> pricing.price(show, byId.get(id))).sum();        if (!payments.charge(userId, total)) {            locks.release(showId, seatIds, userId);            throw new IllegalStateException("Payment failed");        }        if (!locks.confirm(showId, seatIds, userId)) throw new IllegalStateException("Hold expired; payment will be refunded");        return new Booking(UUID.randomUUID().toString(), showId, userId, List.copyOf(seatIds), total);    }}
09

Complexity and performance

Hold k seatsO(k)

Under one lock per show.

AvailabilityO(seats in show)

Hundreds.

10

Trade-offs

Hold duration

Long holds reduce payment failures from expiry but lock inventory; short holds free seats faster but frustrate slow users.

In-memory locks vs database locks

In-memory locks work for one server; multiple servers need database row locks, conditional updates, or Redis locks with expiry.

11

Variants and related techniques

Distributed holds

UPDATE show_seats SET status='HELD' WHERE id IN (...) AND status='AVAILABLE' with a row count check.

Virtual waiting room

Queue users during demand spikes (blockbuster releases).

12

Common mistakes

  • Checking availability and booking in separate steps without locking.

    Fix: Hold atomically for all seats.

  • Holding seats forever when users abandon checkout.

    Fix: Expiring holds with lazy or scheduled release.

  • Partial holds (some seats taken).

    Fix: All-or-nothing hold.

13

Interview questions

How do you prevent double booking?

Acquire an atomic, all-or-nothing hold on the selected show seats with an expiry (synchronized in-memory lock, database conditional update, or distributed lock), confirm only if the hold is still valid and owned by the user, and use unique constraints as a final safety net.

What happens if payment succeeds after the hold expired?

Confirmation fails because the hold is invalid; the system automatically refunds the payment, or extends holds while payment is in progress to make this rare.

14

Practice problems

ProblemDifficultyWhat it trains
Seat booking with holds and expiryMediumConcurrency.
Full BookMyShow with search, pricing, cancellationHardModeling.