Machine Coding Problem

Movie Booking (BookMyShow)

maco30maco60macoAllcommerceconcurrencytransactional-locks
Commonly Asked By:AmazonFlipkartTicketmasterPaytm

Functional Scope (In-Scope)

  • Dynamic Seat Layout: Model various categories (Silver, Gold, VIP) and individual seat layout allocations.
  • Dynamic Weekend/Peak Pricing: Support dynamic seat pricing strategies dynamically responding to show weekend peaks.
  • Two-Phase Reservations: Prevent instant double-booking. Seats are held (RESERVED) with a dynamic TTL (e.g., 10 minutes) allowing transaction flow.
  • Fail-Safe Multi-Seat Rollback: Acquire multiple locks systematically (sorted to prevent deadlocks) and run rolling rollbacks if any single seat fails.
  • Transactional Expiry Cleanup: Automatically release seat holds back to AVAILABLE upon payment expiration or client cancellation.

Explicit Boundaries (Out-of-Scope)

  • No Third-Party Payment Integrations: External credit cards or webhooks are simulated via standard synchronous/asynchronous checkout states.
  • No Real Database Integration: All storage is localized to in-memory safe data structures.

Clean reference designs showing transactional booking mechanics and thread-safety in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

enum SeatCategory { SILVER, GOLD, VIP }
enum SeatStatus { AVAILABLE, RESERVED, BOOKED }
enum BookingState { PENDING, CONFIRMED, CANCELLED, EXPIRED }

interface PricingStrategy {
    double calculatePrice(SeatCategory category, boolean isWeekend);
}

class DynamicPricingStrategy implements PricingStrategy {
    @Override
    public double calculatePrice(SeatCategory category, boolean isWeekend) {
        double base = switch (category) {
            case SILVER -> 100.0;
            case GOLD -> 150.0;
            case VIP -> 250.0;
        };
        return isWeekend ? base * 1.25 : base;
    }
}

class Seat {
    private final String id;
    private final SeatCategory category;
    private SeatStatus status = SeatStatus.AVAILABLE;
    private long reservationTime = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public Seat(String id, SeatCategory category) {
        this.id = id;
        this.category = category;
    }

    public String getId() { return id; }
    public SeatCategory getCategory() { return category; }
    public ReentrantLock getLock() { return lock; }

    public SeatStatus getStatus(long ttlMs) {
        if (status == SeatStatus.RESERVED && (System.currentTimeMillis() - reservationTime > ttlMs)) {
            status = SeatStatus.AVAILABLE;
            reservationTime = 0;
        }
        return status;
    }

    public boolean reserve(long ttlMs) {
        if (getStatus(ttlMs) == SeatStatus.AVAILABLE) {
            status = SeatStatus.RESERVED;
            reservationTime = System.currentTimeMillis();
            return true;
        }
        return false;
    }

    public void book() {
        if (status == SeatStatus.RESERVED) {
            status = SeatStatus.BOOKED;
            reservationTime = 0;
        }
    }

    public void release() {
        if (status == SeatStatus.RESERVED) {
            status = SeatStatus.AVAILABLE;
            reservationTime = 0;
        }
    }
}

class Show {
    private final String id;
    private final String movie;
    private final boolean isWeekend;
    private final Map<String, Seat> seats = new ConcurrentHashMap<>();

    public Show(String id, String movie, boolean isWeekend, int silverCount, int goldCount, int vipCount) {
        this.id = id;
        this.movie = movie;
        this.isWeekend = isWeekend;
        int idx = 1;
        for (int i = 0; i < silverCount; i++) {
            String sid = "S" + idx++;
            seats.put(sid, new Seat(sid, SeatCategory.SILVER));
        }
        for (int i = 0; i < goldCount; i++) {
            String sid = "S" + idx++;
            seats.put(sid, new Seat(sid, SeatCategory.GOLD));
        }
        for (int i = 0; i < vipCount; i++) {
            String sid = "S" + idx++;
            seats.put(sid, new Seat(sid, SeatCategory.VIP));
        }
    }

    public String getId() { return id; }
    public String getMovie() { return movie; }
    public boolean isWeekend() { return isWeekend; }
    public Map<String, Seat> getSeats() { return seats; }
}

class Booking {
    private final String id;
    private final Show show;
    private final List<Seat> seats;
    private final double amount;
    private BookingState state = BookingState.PENDING;
    private final long createdAt;

    public Booking(String id, Show show, List<Seat> seats, double amount) {
        this.id = id;
        this.show = show;
        this.seats = seats;
        this.amount = amount;
        this.createdAt = System.currentTimeMillis();
    }

    public String getId() { return id; }
    public Show getShow() { return show; }
    public List<Seat> getSeats() { return seats; }
    public double getAmount() { return amount; }
    public BookingState getState() { return state; }
    public long getCreatedAt() { return createdAt; }

    public synchronized void confirm() {
        if (state == BookingState.PENDING) {
            state = BookingState.CONFIRMED;
            for (Seat seat : seats) {
                seat.book();
            }
        }
    }

    public synchronized void cancel(BookingState reason) {
        if (state == BookingState.PENDING) {
            state = reason;
            for (Seat seat : seats) {
                seat.release();
            }
        }
    }
}

class BookingService {
    private final Map<String, Booking> bookings = new ConcurrentHashMap<>();
    private final PricingStrategy pricingStrategy = new DynamicPricingStrategy();
    private final long ttlMs;

    public BookingService(long ttlMs) {
        this.ttlMs = ttlMs;
    }

    public Booking createBooking(Show show, List<String> seatIds) {
        List<Seat> targetSeats = new ArrayList<>();
        for (String id : seatIds) {
            Seat s = show.getSeats().get(id);
            if (s == null) return null;
            targetSeats.add(s);
        }

        // Lock sorting to prevent Deadlocks
        targetSeats.sort(Comparator.comparing(Seat::getId));

        List<Seat> acquired = new ArrayList<>();
        boolean success = true;

        for (Seat seat : targetSeats) {
            seat.getLock().lock();
            try {
                if (seat.reserve(ttlMs)) {
                    acquired.add(seat);
                } else {
                    success = false;
                    break;
                }
            } finally {
                seat.getLock().unlock();
            }
        }

        if (!success) {
            for (Seat seat : acquired) {
                seat.getLock().lock();
                try {
                    seat.release();
                } finally {
                    seat.getLock().unlock();
                }
            }
            return null;
        }

        double totalAmount = 0;
        for (Seat seat : targetSeats) {
            totalAmount += pricingStrategy.calculatePrice(seat.getCategory(), show.isWeekend());
        }

        String bookingId = "BKG-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
        Booking booking = new Booking(bookingId, show, targetSeats, totalAmount);
        bookings.put(bookingId, booking);
        return booking;
    }

    public boolean confirmBooking(String bookingId) {
        Booking booking = bookings.get(bookingId);
        if (booking == null) return false;

        synchronized (booking) {
            if (booking.getState() != BookingState.PENDING) return false;
            if (System.currentTimeMillis() - booking.getCreatedAt() > ttlMs) {
                booking.cancel(BookingState.EXPIRED);
                return false;
            }
            booking.confirm();
            return true;
        }
    }

    public void cancelBooking(String bookingId) {
        Booking booking = bookings.get(bookingId);
        if (booking == null) return;
        synchronized (booking) {
            booking.cancel(BookingState.CANCELLED);
        }
    }

    public void cleanupExpiredBookings() {
        for (Booking booking : bookings.values()) {
            synchronized (booking) {
                if (booking.getState() == BookingState.PENDING &&
                    (System.currentTimeMillis() - booking.getCreatedAt() > ttlMs)) {
                    booking.cancel(BookingState.EXPIRED);
                }
            }
        }
    }
}

// ─── DRIVER CLASS ──────────────────────────────────────────────────────────
public class BMSDriver {
    public static void main(String[] args) throws Exception {
        System.out.println("=== MOVIE TICKET BOOKING (BMS) SIMULATION ===");
        BookingService service = new BookingService(1500); // 1.5 seconds TTL for fast demo
        Show avengersWeekend = new Show("SHOW-101", "Avengers: Endgame", true, 5, 3, 2);

        System.out.println("Show Created: " + avengersWeekend.getMovie() + " (Weekend: " + avengersWeekend.isWeekend() + ")");

        // User A tries to reserve S1, S2
        Booking bookingA = service.createBooking(avengersWeekend, Arrays.asList("S1", "S2"));
        System.out.println("User A Reserved S1, S2 -> Booking ID: " + bookingA.getId() + ", Amount: $" + bookingA.getAmount());

        // Concurrent Thread trying to reserve S2, S3 (should fail due to S2 being reserved)
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<Booking> userBTask = executor.submit(() -> service.createBooking(avengersWeekend, Arrays.asList("S2", "S3")));

        Booking bookingB = userBTask.get();
        System.out.println("User B Attempt to reserve S2, S3 -> Result: " + (bookingB != null ? bookingB.getId() : "FAILED (Conflict - Rollback worked)"));

        // User A pays and confirms booking
        boolean confirmedA = service.confirmBooking(bookingA.getId());
        System.out.println("User A Payment Confirmation -> " + (confirmedA ? "SUCCESS" : "FAILED"));

        // User C reserves S4, S5
        Booking bookingC = service.createBooking(avengersWeekend, Arrays.asList("S4", "S5"));
        System.out.println("User C Reserved S4, S5 -> Booking ID: " + bookingC.getId() + ", Pending Payment...");

        // Wait to simulate TTL expiry
        System.out.println("Waiting 2 seconds for User C's hold to expire...");
        Thread.sleep(2000);

        service.cleanupExpiredBookings();
        System.out.println("Booking C State: " + bookingC.getState());

        // User D tries S4, S5 now
        Booking bookingD = service.createBooking(avengersWeekend, Arrays.asList("S4", "S5"));
        System.out.println("User D tries S4, S5 (after C expired) -> Result: " + (bookingD != null ? "SUCCESS (Booking ID: " + bookingD.getId() + ")" : "FAILED"));

        executor.shutdown();
    }
}