← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›LLD Interview Prep›LLD
🎬

Design BookMyShow

Movie ticket booking — seat locking, show/screen model, and payment seam. A staple at Paytm, BookMyShow-style companies, and Amazon/Flipkart LLD loops.

📊 Medium⏱ 40–50 min🏢 Paytm · BookMyShow · Amazon · Flipkart · Swiggy (inventory-style LLD)
🎯 Interview goal

Model Show → Screen → Seat with a BookingService facade. Lock seats (short TTL) then confirm after payment. Use Strategy for payment and synchronized seat transitions to prevent double booking.

📋

The interview question

“Design a movie ticket booking system like BookMyShow.” Support browsing shows, selecting seats, temporary hold, payment, and confirmation.

Interviewers care about seat concurrency (two users, same seat) and a clean facade — not building Netflix streaming.

💡
Clarify city/theater scope and whether you need search — most loops want book seats for one show end-to-end.
❓

Clarifying questions (ask before coding)

1Single theater or multi-city catalog?
Why askDecides if you need City / Theater or only Show + Screen.
2Seat lock duration before payment?
Why askDrives LOCKED state + TTL (e.g. 5–10 min); mention scheduler or lazy expiry.
3Payment methods — UPI, card, wallet?
Why askNatural Strategy; keep v1 as pay() returning boolean.
4Can user book partial row / premium seats?
Why askSeat types and pricing tiers — optional SeatCategory enum.
✅

Requirements & assumptions (state aloud)

  • Functional: list seats for a show; lock selected seats; confirm booking after payment; release lock on timeout/cancel.
  • Seat lifecycle: AVAILABLE → LOCKED → BOOKED (no skip).
  • Structure: Show owns Screen layout; Seat is atomic unit for locking.
  • v1: in-memory, single JVM; no distributed cache unless asked.
  • Out of scope: recommendations, refunds, waitlist — mention as extensions.
🧩

High-level design & class relationships

BookingService is the facade — lockSeats(user, show, ids) then confirmBooking(bookingId, payment).

Per-show lock on seat map or per-seat synchronized methods — say which you chose.

  1. Enums: SeatStatus
  2. Domain: Seat, Screen, Show, User, Booking
  3. Services: BookingService, PaymentStrategy
A1A2A3A4

🟢 AVAILABLE 🟡 LOCKED (TTL) 🔴 BOOKED

BookMyShow booking flow
User → lock → pay → BOOKED
📐

UML class diagram (optional)

Sketch 6–7 classes if asked — focus on seat state and booking facade.

BookMyShow UML classes
Main classes (relationships verbal on board)

Relationships to label aloud

  • Composition: Show → Seat collection tied to one screening.
  • Association: Booking → User + List<Seat>.
  • Strategy: PaymentStrategy implementations.
🎨

Design patterns used (and why)

PatternWhereWhy (interview soundbite)
🪞FacadeBookingServiceOne entry for lock + confirm; hides seat map details.
🎯StrategyPaymentStrategySwap UPI/card without touching booking flow.
🎬StateSeat status enumIllegal transitions (book without lock) rejected in Seat.
🔒Lock / syncper seat or showPrevents double booking under concurrent users.
💻

Full solution — domain model

1. Seat status & seatjava
public enum SeatStatus { AVAILABLE, LOCKED, BOOKED }

public class Seat {
    private final String seatId;
    private final int row;
    private final int col;
    private SeatStatus status = SeatStatus.AVAILABLE;
    private String lockedBy;
    private Instant lockExpiry;

    // Creates a seat at row/col with AVAILABLE status
    public Seat(String seatId, int row, int col) {
        this.seatId = seatId;
        this.row = row;
        this.col = col;
    }

    // Holds seat for user until TTL; false if another user holds it
    public synchronized boolean tryLock(String userId, Duration ttl) {
        if (status != SeatStatus.AVAILABLE) return false;
        if (status == SeatStatus.LOCKED && lockExpiry != null && Instant.now().isBefore(lockExpiry))
            return lockedBy != null && lockedBy.equals(userId);
        status = SeatStatus.LOCKED;
        lockedBy = userId;
        lockExpiry = Instant.now().plus(ttl);
        return true;
    }

    // After payment: moves LOCKED → BOOKED for same user; false if lock expired
    public synchronized boolean confirmBook(String userId) {
        if (status != SeatStatus.LOCKED || !userId.equals(lockedBy)) return false;
        if (lockExpiry != null && Instant.now().isAfter(lockExpiry)) {
            releaseLock();
            return false;
        }
        status = SeatStatus.BOOKED;
        lockedBy = null;
        lockExpiry = null;
        return true;
    }

    // Cancels hold and returns seat to AVAILABLE
    public synchronized void releaseLock() {
        if (status == SeatStatus.LOCKED) {
            status = SeatStatus.AVAILABLE;
            lockedBy = null;
            lockExpiry = null;
        }
    }

    // True when seat can be selected for locking
    public boolean isAvailable() { return status == SeatStatus.AVAILABLE; }

    // Returns seat id used in booking requests
    public String getSeatId() { return seatId; }
}
2. Show & screenjava
public class Screen {
    private final String screenId;
    private final String name;

    // Physical screen/auditorium for a show
    public Screen(String screenId, String name) {
        this.screenId = screenId;
        this.name = name;
    }
}

public class Show {
    private final String showId;
    private final Screen screen;
    private final Instant startTime;
    private final Map<String, Seat> seats = new LinkedHashMap<>();

    // One movie screening with seat map
    public Show(String showId, Screen screen, Instant startTime) {
        this.showId = showId;
        this.screen = screen;
        this.startTime = startTime;
    }

    // Adds a seat to this show's layout
    public void addSeat(Seat seat) { seats.put(seat.getSeatId(), seat); }

    // Read-only seat map for UI and booking service
    public Map<String, Seat> getSeats() { return Collections.unmodifiableMap(seats); }

    // Returns show id for booking records
    public String getShowId() { return showId; }
}
⚙️

Full solution — booking & payment

3. Booking service (facade)java
public class Booking {
    private final String bookingId = UUID.randomUUID().toString();
    private final String userId;
    private final Show show;
    private final List<String> seatIds;
    private BigDecimal amount;
    private boolean confirmed;

    // Pending booking after seats are locked, before payment
    public Booking(String userId, Show show, List<String> seatIds, BigDecimal amount) {
        this.userId = userId;
        this.show = show;
        this.seatIds = List.copyOf(seatIds);
        this.amount = amount;
    }

    // Id passed to confirmBooking after lock
    public String getBookingId() { return bookingId; }
    // Owner of the lock / booking
    public String getUserId() { return userId; }
    // Show where seats are held
    public Show getShow() { return show; }
    // Locked seat ids for confirm or rollback
    public List<String> getSeatIds() { return seatIds; }
    // Total charge sent to payment strategy
    public BigDecimal getAmount() { return amount; }
    // True after payment and seats marked BOOKED
    public boolean isConfirmed() { return confirmed; }

    // Marks booking successful after payment and seat confirmation
    void markConfirmed() { this.confirmed = true; }
}

public class BookingService {
    private static final Duration LOCK_TTL = Duration.ofMinutes(10);
    private final Map<String, Booking> pending = new ConcurrentHashMap<>();

    // Locks all requested seats; rolls back on any failure; returns pending booking
    public Optional<Booking> lockSeats(String userId, Show show, List<String> seatIds, BigDecimal pricePerSeat) {
        List<Seat> toLock = new ArrayList<>();
        for (String id : seatIds) {
            Seat seat = show.getSeats().get(id);
            if (seat == null || !seat.tryLock(userId, LOCK_TTL)) {
                toLock.forEach(s -> s.releaseLock());
                return Optional.empty();
            }
            toLock.add(seat);
        }
        BigDecimal total = pricePerSeat.multiply(BigDecimal.valueOf(seatIds.size()));
        Booking b = new Booking(userId, show, seatIds, total);
        pending.put(b.getBookingId(), b);
        return Optional.of(b);
    }

    // Charges payment then BOOKED; releases locks if payment or confirm fails
    public synchronized boolean confirmBooking(String bookingId, PaymentStrategy payment) {
        Booking b = pending.remove(bookingId);
        if (b == null || b.isConfirmed()) return false;
        if (!payment.pay(b.getAmount())) {
            releaseSeats(b);
            return false;
        }
        for (String id : b.getSeatIds()) {
            Seat seat = b.getShow().getSeats().get(id);
            if (!seat.confirmBook(b.getUserId())) {
                releaseSeats(b);
                return false;
            }
        }
        b.markConfirmed();
        return true;
    }

    // Releases locks on all seats in a failed or cancelled booking
    private void releaseSeats(Booking b) {
        for (String id : b.getSeatIds())
            b.getShow().getSeats().get(id).releaseLock();
    }
}
4. Payment strategy + demojava
public interface PaymentStrategy {
    // Processes payment; true if successful
    boolean pay(BigDecimal amount);
}

public class UPIPayment implements PaymentStrategy {
    // Stub UPI charge — replace with gateway integration
    @Override
    public boolean pay(BigDecimal amount) {
        return amount.signum() > 0;
    }
}

public class BookMyShowDemo {
    // End-to-end: lock seat → confirm with UPI
    public static void main(String[] args) {
        Screen screen = new Screen("S1", "IMAX");
        Show show = new Show("SH1", screen, Instant.now().plusHours(2));
        show.addSeat(new Seat("A1", 1, 1));
        show.addSeat(new Seat("A2", 1, 2));

        BookingService svc = new BookingService();
        Optional<Booking> lock = svc.lockSeats("user1", show, List.of("A1"), new BigDecimal("250"));
        lock.ifPresent(b -> {
            boolean ok = svc.confirmBooking(b.getBookingId(), new UPIPayment());
            System.out.println(ok ? "Booked " + b.getBookingId() : "Failed");
        });
    }
}
🎤

While you code — questions interviewers ask

🚀

Extensions & follow-ups

  • Waitlist when show sold out.
  • Dynamic pricing by SeatCategory.
  • Idempotent confirm with paymentId dedup.
⚠️

Common mistakes (fail signals)

  • Booking without lock step — race on hot seats.
  • No rollback when one seat in a multi-seat request fails.
  • God class holding UI + DB + payment.
🧾

60-second cheat sheet

Ask firstLock TTL, payment types, multi-city?
Core classesSeat, Show, Booking, BookingService
Flowlock → pay → confirm → BOOKED
PatternsFacade, Strategy, State on seat
Concurrencysync per seat; release all on partial failure
🔗

More LLD problems