LLD for Movie Booking System: Designing BookMyShow
Designing a movie booking system involves handling concurrency (no double bookings!), managing complex hierarchies, and ensuring a smooth user experie
Abstract AlgorithmsTLDR: A Movie Booking System (like BookMyShow) is an inventory management problem with an expiry: seats expire when the show starts. The core engineering challenge is preventing double-booking under concurrent user load with a 3-state seat model (AVAILABLE โ LOCKED โ BOOKED).
๐ The Cinema Inventory Problem: What Makes It Hard
You run a cinema. A Friday night show has 200 seats. Two scenarios break a naive implementation:
- Double-booking: User A and User B click "Book Seat A1" at the same millisecond. Only one should succeed.
- Abandoned locks: User A locks Seat A1 but never completes payment. The seat should become available again after 10 minutes.
This needs a 3-state model + distributed lock strategy.
๐ข The Domain Hierarchy: City โ Cinema โ Screen โ Show โ Seat
classDiagram
class City { +String name; +List~Cinema~ cinemas }
class Cinema { +String name; +List~Screen~ screens }
class Screen { +int id; +List~Seat~ seats }
class Movie { +String title; +int durationMinutes }
class Show {
+Movie movie
+Screen screen
+LocalDateTime startTime
+synchronized boolean bookSeats(List seats)
}
class Seat {
+String row
+int col
+SeatType type
+SeatStatus status
}
class Booking {
+int id
+Show show
+List~Seat~ seats
+PaymentStatus payment
}
City *-- Cinema
Cinema *-- Screen
Screen *-- Seat
Show --> Screen
Booking --> Show
Booking --> Seat
- SeatType:
GOLD,SILVER,PLATINUMโ affects pricing. - SeatStatus:
AVAILABLE,LOCKED,BOOKEDโ the concurrency core. - Show is the intersection of Movie + Screen + StartTime.
โ๏ธ The 3-State Seat Model: Preventing Double Booking
The key insight: don't go directly from AVAILABLE to BOOKED. Insert a temporary LOCKED state.
State machine:
stateDiagram-v2
[*] --> AVAILABLE
AVAILABLE --> LOCKED : User selects seat (10 min TTL)
LOCKED --> BOOKED : Payment success
LOCKED --> AVAILABLE : Payment timeout / failure
BOOKED --> [*] : Show complete
The booking flow:
| Time | User A | User B | Seat A1 Status |
| 10:00:00 | Selects A1 | โ | AVAILABLE |
| 10:00:01 | System locks A1 | โ | LOCKED (expires 10:10) |
| 10:00:02 | Directed to payment | Selects A1 | โ |
| 10:00:02 | โ | System: seat unavailable | LOCKED (no change) |
| 10:05:00 | Payment success | โ | BOOKED |
User B gets a graceful "This seat is currently being held by another user" message โ not a race condition with undefined behavior.
๐ง Java Implementation: Synchronized Booking
public class Show {
private final int id;
private final List<Seat> seats;
public synchronized BookingResult reserveSeats(User user, List<Integer> seatIds) {
// Phase 1: Check all requested seats are available
List<Seat> selected = new ArrayList<>();
for (int id : seatIds) {
Seat s = findSeat(id);
if (s.getStatus() != SeatStatus.AVAILABLE) {
return BookingResult.failure("Seat " + id + " is not available");
}
selected.add(s);
}
// Phase 2: Lock all seats together (atomic within synchronized block)
Instant lockExpiry = Instant.now().plusSeconds(600);
for (Seat s : selected) {
s.setStatus(SeatStatus.LOCKED);
s.setLockExpiry(lockExpiry);
}
return BookingResult.success(new Booking(user, this, selected));
}
}
synchronized on the Show method ensures that the check-and-lock is atomic โ no two threads can interleave between the check (AVAILABLE) and the lock (LOCKED).
โ๏ธ Pricing Strategy Pattern
Different shows and seat types need different prices:
public interface PricingStrategy {
double calculatePrice(Show show, Seat seat);
}
public class HourlyPricing implements PricingStrategy {
public double calculatePrice(Show show, Seat seat) {
double base = seat.getType() == SeatType.GOLD ? 15.0 : 10.0;
return show.isWeekend() ? base * 1.2 : base; // weekend surcharge
}
}
public class DynamicPricing implements PricingStrategy {
public double calculatePrice(Show show, Seat seat) {
double occupancy = show.getBookedPercent();
return BASE_PRICE * (1 + occupancy); // price rises with demand
}
}
โ๏ธ Scaling the Booking System
The synchronized approach works for a single JVM. At scale:
| Scale | Approach | |
| Single server | synchronized on Show | O(1) per booking; blocks concurrent requests to same show |
| Multi-server | Distributed lock (Redis SETNX with TTL) | Lock key = show:{id}:seat:{id} |
| High scale | Optimistic locking with DB CAS (UPDATE seat SET status='LOCKED' WHERE status='AVAILABLE' AND id=?) | No lock held between check and update |
| Seat expiry | Scheduled job (every minute) reverts LOCKED โ AVAILABLE where lock_expiry < NOW() | Clears abandoned holds |
๐ Summary
- 3-state seat model (AVAILABLE โ LOCKED โ BOOKED) prevents double-booking and handles abandoned checkouts.
synchronized bookSeats()makes the check-and-lock atomic; no two users can hold the same seat.- Strategy Pattern for pricing decouples business rules from booking logic.
- Distributed scale moves from JVM
synchronizedto Redis SETNX or DB CAS.
๐ Practice Quiz
Why is a 3-state model (AVAILABLE โ LOCKED โ BOOKED) better than a 2-state model (AVAILABLE โ BOOKED)?
- A) It reduces database writes.
- B) The LOCKED state holds the seat for the user during payment without permanently booking it, allowing auto-release on timeout.
- C) It's required by cinema software standards.
Answer: B
In the single-server Java implementation, what prevents two threads from both seeing a seat as AVAILABLE and both locking it?
- A)
volatileon the seat status field. - B)
synchronizedonbookSeats()โ only one thread executes the check-and-lock block at a time. - C) A database constraint.
Answer: B
- A)
At multi-server scale, which mechanism replaces Java's
synchronizedkeyword for preventing double booking?- A) Thread pools with fixed queue size.
- B) Distributed lock via Redis SETNX (or DB optimistic locking with CAS).
- C) Read replicas for the seat table.
Answer: B

Written by
Abstract Algorithms
@abstractalgorithms
More Posts
System Design Service Discovery and Health Checks: Routing Traffic to Healthy Instances
TLDR: Service discovery is how clients find the right service instance at runtime, and health checks are how systems decide whether an instance should receive traffic. Together, they turn dynamic infrastructure from guesswork into deterministic routi...
System Design Roadmap: A Complete Learning Path from Basics to Advanced Architecture
TLDR: This roadmap organizes every system-design-tagged post in this repository into learning groups and a recommended order. It is designed for interview prep and practical architecture thinking, from fundamentals to scaling, reliability, and implem...
System Design Observability, SLOs, and Incident Response: Operating Systems You Can Trust
TLDR: Observability is how you understand system behavior from telemetry, SLOs are explicit reliability targets, and incident response is the execution model when those targets are at risk. Together, they convert operational chaos into measurable, re...
System Design Message Queues and Event-Driven Architecture: Building Reliable Asynchronous Systems
TLDR: Message queues and event-driven architecture let services communicate asynchronously, absorb bursty traffic, and isolate failures. The core design challenge is not adding a queue. It is defining delivery semantics, retry behavior, and idempoten...
