📌Problem Statement
Design an online movie ticket booking system like BookMyShow or Fandango.
📌Requirements
Functional
Non-Functional
📌Class Design
Enums
public enum SeatType { REGULAR, PREMIUM, VIP}public enum BookingStatus { PENDING, CONFIRMED, CANCELLED}public enum PaymentStatus { PENDING, SUCCESS, FAILED, REFUNDED}Core Entities
public class Movie { private String movieId; private String title; private String description; private int durationMinutes; private String language; private String genre; private LocalDate releaseDate;}public class Theater { private String theaterId; private String name; private String address; private City city; private List<Screen> screens;}public class Screen { private String screenId; private String name; private Theater theater; private List<Seat> seats;}public class Seat { private String seatId; private int row; private int column; private SeatType type; private Screen screen;}Show and ShowSeat
public class Show { private String showId; private Movie movie; private Screen screen; private LocalDateTime startTime; private LocalDateTime endTime; private Map<String, ShowSeat> showSeats; public Show(Movie movie, Screen screen, LocalDateTime startTime) { this.showSeats = new HashMap<>(); for (Seat seat : screen.getSeats()) { showSeats.put(seat.getSeatId(), new ShowSeat(seat, this)); } }}public class ShowSeat { private Seat seat; private Show show; private boolean isBooked; private double price; private String lockedBy; private LocalDateTime lockExpiry; public synchronized boolean tryLock(String userId, int lockMinutes) { if (isBooked) return false; if (lockedBy != null && lockExpiry.isAfter(LocalDateTime.now())) { return lockedBy.equals(userId); } this.lockedBy = userId; this.lockExpiry = LocalDateTime.now().plusMinutes(lockMinutes); return true; } public synchronized void unlock() { this.lockedBy = null; this.lockExpiry = null; } public synchronized boolean book() { if (isBooked) return false; this.isBooked = true; this.lockedBy = null; return true; }}Booking System
public class Booking { private String bookingId; private User user; private Show show; private List<ShowSeat> seats; private BookingStatus status; private Payment payment; private LocalDateTime bookingTime; private double totalAmount; public Booking(User user, Show show, List<ShowSeat> seats) { this.bookingId = UUID.randomUUID().toString(); this.user = user; this.show = show; this.seats = seats; this.status = BookingStatus.PENDING; this.totalAmount = seats.stream() .mapToDouble(ShowSeat::getPrice) .sum(); }}Booking Service with Concurrency
public class BookingService { private static final int LOCK_TIMEOUT_MINUTES = 10; private Map<String, Booking> bookings; private PaymentService paymentService; public Booking initiateBooking(User user, Show show, List<String> seatIds) { List<ShowSeat> seats = new ArrayList<>(); for (String seatId : seatIds) { ShowSeat showSeat = show.getShowSeats().get(seatId); if (showSeat == null) { throw new SeatNotFoundException(seatId); } if (!showSeat.tryLock(user.getUserId(), LOCK_TIMEOUT_MINUTES)) { unlockSeats(seats, user.getUserId()); throw new SeatNotAvailableException(seatId); } seats.add(showSeat); } Booking booking = new Booking(user, show, seats); bookings.put(booking.getBookingId(), booking); return booking; } public Booking confirmBooking(String bookingId, PaymentDetails paymentDetails) { Booking booking = bookings.get(bookingId); Payment payment = paymentService.process(paymentDetails, booking.getTotalAmount()); if (payment.getStatus() == PaymentStatus.SUCCESS) { for (ShowSeat seat : booking.getSeats()) { seat.book(); } booking.setStatus(BookingStatus.CONFIRMED); booking.setPayment(payment); } else { unlockSeats(booking.getSeats(), booking.getUser().getUserId()); throw new PaymentFailedException(); } return booking; } private void unlockSeats(List<ShowSeat> seats, String userId) { for (ShowSeat seat : seats) { seat.unlock(); } }}📌Handling Race Conditions
The key challenge is preventing double booking:
We use approach 3 with tryLock() method.
📌Database Schema
CREATE TABLE bookings ( booking_id VARCHAR(50) PRIMARY KEY, user_id VARCHAR(50), show_id VARCHAR(50), status VARCHAR(20), total_amount DECIMAL(10,2), booking_time TIMESTAMP);CREATE TABLE booking_seats ( booking_id VARCHAR(50), seat_id VARCHAR(50), price DECIMAL(10,2), PRIMARY KEY (booking_id, seat_id));📌Interview Tips
Asked at Flipkart, Swiggy, and Paytm interviews.