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.
// 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.
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.
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 AriaHow would you implement a seat lock expiry so a user who abandons payment releases seats after 10 minutes?
Why is Singleton appropriate for BookingSystem? What are the distributed-system trade-offs?
How would you scale this design to support millions of concurrent users on opening day of a blockbuster?
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.