SweatyImposterMovie Ticket Booking

Movie Ticket Booking

Design a movie ticket booking system: theaters with multiple screens, movies showing at scheduled times, seat selection with temporary holds during checkout, and payment that confirms the booking.

Clarifying questions

  • Single theater chain or a marketplace across chains?
  • How long is the seat-hold TTL (Time To Live), and what happens if payment is slow?
  • Are seats tiered by price (standard, premium, recliner)?
  • Do we model concessions in this iteration?
  • Are we doing assigned seating only, or also general admission?

Core requirements

  • Browse movies and showtimes by city, theater, and date.
  • View seat map for a specific show with real-time availability.
  • Hold selected seats with a TTL during checkout.
  • Charge payment and confirm booking atomically with the hold.
  • Release holds on TTL expiry or explicit cancel.
  • Issue a confirmed ticket with seats, show, and barcode.

Canonical class breakdown

MovieFilm catalog entry: title, duration, rating, language; independent of any showing.
TheaterPhysical venue that owns one or more Screens and a location.
ScreenAuditorium with a fixed seat layout; hosts Shows over time.
ShowA specific Movie on a specific Screen at a specific time; owns the per-seat availability for that showing.
SeatPosition in a Screen's layout with a tier (standard/premium); price varies by Show.
SeatHoldTime-bound reservation against a set of seats on a Show, owned by a user session, with an expiry.
BookingConfirmed transaction created from a SeatHold after successful payment; immutable.
BookingServiceOrchestrates seat-select -> hold -> pay -> confirm; the only path that turns a hold into a Booking.
PaymentGateway (interface)Pluggable payment provider with idempotent charge for retry safety.

Java sketchjava

// Java skeleton

enum SeatTier { STANDARD, PREMIUM, RECLINER }
enum SeatStatus { AVAILABLE, HELD, BOOKED }
enum BookingStatus { CONFIRMED, CANCELLED, REFUNDED }

class Movie {
    private final String movieId;
    private final String title;
    private final int durationMinutes;
    private final String rating;
    private final String language;
}

class Seat {
    private final String seatId;
    private final int row;
    private final int col;
    private final SeatTier tier;
}

class Screen {
    private final String screenId;
    private final List<Seat> layout;
}

class Theater {
    private final String theaterId;
    private final String name;
    private final String city;
    private final List<Screen> screens;
}

class Show {
    private final String showId;
    private final Movie movie;
    private final Screen screen;
    private final Instant startTime;
    private final Map<SeatTier, BigDecimal> priceByTier;
    private final Map<String, SeatStatus> seatStatus; // contended resource
    public boolean tryHold(Set<String> seatIds) { /* atomic CAS over the map */ }
    public void release(Set<String> seatIds) { ... }
    public void confirm(Set<String> seatIds) { ... }
}

class SeatHold {
    private final String holdId;
    private final Show show;
    private final Set<String> seatIds;
    private final String userId;
    private final Instant expiresAt;
    public boolean isExpired() { ... }
}

class Booking {
    private final String bookingId;
    private final Show show;
    private final Set<String> seatIds;
    private final String userId;
    private final BigDecimal totalCharged;
    private final String barcode;
    private BookingStatus status;
}

interface PaymentGateway {
    PaymentResult charge(String idempotencyKey, BigDecimal amount, String userId);
}

class BookingService {
    private final PaymentGateway gateway;
    private final Map<String, SeatHold> activeHolds;
    public SeatHold hold(Show show, Set<String> seatIds, String userId, Duration ttl) { ... }
    public Booking confirm(SeatHold hold, String paymentToken) { /* charge then convert */ }
    public void releaseExpired(Instant now) { ... }
}

Key decisions to defend

  • SeatHold lives on the Show (the contended resource), not on the user — that's where the lock must be.
  • Hold TTL is the entire concurrency story: short enough to free abandoned carts, long enough to let payment land.
  • Use atomic check-and-set on the per-show seat map to create holds; a list of holds with scans does not scale.
  • Booking is immutable and created only via BookingService; never let the client mutate hold -> booking directly.
  • Pricing lives on (Show, SeatTier), not on Seat — same physical seat costs different amounts on Friday night.

Likely follow-ups

  • · Two users tap 'pay' for overlapping seats simultaneously — walk me through the race.
  • · Refunds and seat-swap — how do you keep the seat map consistent?
  • · Pre-sale a blockbuster with 10k concurrent requests for the same show — what breaks?

Dive deeper

Movie booking is the canonical hold-with-TTL design question. The whole problem hinges on where the lock lives (the Show) and how you make the hold atomic. Candidates who put holds on the user or scan a list of holds fail under any real load. The deeper test is whether you treat the hold-then-confirm flow as a state machine with explicit expiry, rather than a sequence of optimistic operations that hope nothing else happened in between.

All OOD prompts