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.
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.
Clarifying questions (ask before coding)
City / Theater or only Show + Screen.pay() returning boolean.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:
ShowownsScreenlayout;Seatis 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.
- Enums:
SeatStatus - Domain:
Seat,Screen,Show,User,Booking - Services:
BookingService,PaymentStrategy
🟢 AVAILABLE 🟡 LOCKED (TTL) 🔴 BOOKED
UML class diagram (optional)
Sketch 6–7 classes if asked — focus on seat state and booking facade.
Relationships to label aloud
- Composition:
Show→Seatcollection tied to one screening. - Association:
Booking→User+List<Seat>. - Strategy:
PaymentStrategyimplementations.
Design patterns used (and why)
| Pattern | Where | Why (interview soundbite) |
|---|---|---|
| 🪞Facade | BookingService | One entry for lock + confirm; hides seat map details. |
| 🎯Strategy | PaymentStrategy | Swap UPI/card without touching booking flow. |
| 🎬State | Seat status enum | Illegal transitions (book without lock) rejected in Seat. |
| 🔒Lock / sync | per seat or show | Prevents double booking under concurrent users. |
Full solution — domain model
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; }
}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
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();
}
}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
paymentIddedup.
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.