Overview
Design a movie ticket booking system like BookMyShow. Requirements: cities have cinemas with screens; screens have seat layouts (regular, premium); movies have shows at times on screens; users search shows, view seat availability, select seats, hold them for a few minutes while paying, and receive a booking; failed or abandoned payments release seats; cancellations follow a refund policy.
The heart of the problem is concurrency: two users must never book the same seat. The standard design uses a seat hold (lock) with an expiry per show seat, acquired atomically for all selected seats, confirmed on successful payment, and released on failure or timeout. Pricing is a strategy (seat type, time of day, weekend), and payments use an adapter.
When you pick seats, the cashier puts a 'reserved for 10 minutes' tag on them. Nobody else can take them while you pay. If you walk away, the tags expire and the seats return to the shelf.
When to use it
- Top LLD interview problem combining modeling and concurrency.
- Any reservation system with limited inventory (flights, hotels, events).
- Practicing atomic holds with expiry.
Where it shows up in interviews
Recognize it when: limited items, payment takes time.
- Design a movie ticket system
- Design a flight booking system
- Design a concert ticketing system
Recognize it when: many users compete for the same seats.
- Design BookMyShow
- Design a hotel reservation system
Where it is used in real software
Hold selected seats for a few minutes during checkout.
Uses virtual queues and holds to handle huge demand spikes.
Seat holds and fare classes follow similar patterns in reservation systems.
Key terms
- Show
- A movie on a screen at a time.
- ShowSeat
- A seat for a specific show with status (available, held, booked).
- Seat hold
- Temporary exclusive lock with expiry.
- Booking
- Confirmed seats with payment reference.
- Pricing strategy
- Computes price by seat type and show time.
How it works, step by step
- 1Clarify
Search scope, seat types, hold duration, payment, cancellation.
- 2Entities
City, Cinema, Screen, Seat, Movie, Show, ShowSeat, Hold, Booking, Payment.
- 3Hold seats atomically
All requested seats or none; expiry timestamp.
- 4Confirm or release
Payment success books; failure or timeout releases.
- 5Cancellation
Refund policy strategy, seats become available.
STEP 1User 1 holds A5 and A6 atomically, expiring in 10 minutes.
Seat state transitions
ShowSeat lifecycle
| From | Event | To | Condition |
|---|---|---|---|
| AVAILABLE | hold(user) | HELD | All requested seats available |
| HELD | confirm(user, payment ok) | BOOKED | Hold not expired and owned by user |
| HELD | expire / payment failed | AVAILABLE | - |
| BOOKED | cancel(user) | AVAILABLE | Refund per policy |
NOWFrom: AVAILABLE | Event: hold(user) | To: HELD | Condition: All requested seats available
Holds make the slow payment step safe without locking seats forever.
Implementation
import java.time.*;import java.util.*; enum SeatType { REGULAR, PREMIUM }record Seat(String id, SeatType type) {}record Show(String id, String movie, LocalDateTime start, List<Seat> seats) {}record Booking(String id, String showId, String userId, List<String> seatIds, long totalCents) {} interface PricingStrategy { long price(Show show, Seat seat); }final class StandardPricing implements PricingStrategy { public long price(Show show, Seat seat) { long base = seat.type() == SeatType.PREMIUM ? 1500 : 1000; DayOfWeek d = show.start().getDayOfWeek(); return (d == DayOfWeek.SATURDAY || d == DayOfWeek.SUNDAY) ? base * 12 / 10 : base; }} interface PaymentGateway { boolean charge(String userId, long cents); } final class SeatLockManager { private record Hold(String userId, Instant expiresAt) {} private final Map<String, Hold> holds = new HashMap<>(); // key: showId:seatId private final Set<String> booked = new HashSet<>(); private final Clock clock; private final Duration ttl; SeatLockManager(Clock clock, Duration ttl) { this.clock = clock; this.ttl = ttl; } synchronized boolean hold(String showId, List<String> seatIds, String userId) { Instant now = clock.instant(); for (String s : seatIds) { // check all first: all-or-nothing String k = showId + ":" + s; Hold h = holds.get(k); boolean heldByOther = h != null && h.expiresAt().isAfter(now) && !h.userId().equals(userId); if (booked.contains(k) || heldByOther) return false; } seatIds.forEach(s -> holds.put(showId + ":" + s, new Hold(userId, now.plus(ttl)))); return true; } synchronized boolean confirm(String showId, List<String> seatIds, String userId) { Instant now = clock.instant(); for (String s : seatIds) { Hold h = holds.get(showId + ":" + s); if (h == null || !h.userId().equals(userId) || !h.expiresAt().isAfter(now)) return false; } seatIds.forEach(s -> { holds.remove(showId + ":" + s); booked.add(showId + ":" + s); }); return true; } synchronized void release(String showId, List<String> seatIds, String userId) { seatIds.forEach(s -> holds.computeIfPresent(showId + ":" + s, (k, h) -> h.userId().equals(userId) ? null : h)); } synchronized boolean isAvailable(String showId, String seatId) { String k = showId + ":" + seatId; Hold h = holds.get(k); return !booked.contains(k) && (h == null || !h.expiresAt().isAfter(clock.instant())); }} final class BookingService { private final Map<String, Show> shows = new HashMap<>(); private final SeatLockManager locks; private final PricingStrategy pricing; private final PaymentGateway payments; BookingService(SeatLockManager locks, PricingStrategy pricing, PaymentGateway payments) { this.locks = locks; this.pricing = pricing; this.payments = payments; } void addShow(Show s) { shows.put(s.id(), s); } List<Seat> availableSeats(String showId) { return shows.get(showId).seats().stream().filter(s -> locks.isAvailable(showId, s.id())).toList(); } Booking book(String userId, String showId, List<String> seatIds) { Show show = Optional.ofNullable(shows.get(showId)).orElseThrow(() -> new IllegalArgumentException("Unknown show")); Map<String, Seat> byId = new HashMap<>(); show.seats().forEach(s -> byId.put(s.id(), s)); if (!byId.keySet().containsAll(seatIds)) throw new IllegalArgumentException("Unknown seat"); if (!locks.hold(showId, seatIds, userId)) throw new IllegalStateException("One or more seats unavailable"); long total = seatIds.stream().mapToLong(id -> pricing.price(show, byId.get(id))).sum(); if (!payments.charge(userId, total)) { locks.release(showId, seatIds, userId); throw new IllegalStateException("Payment failed"); } if (!locks.confirm(showId, seatIds, userId)) throw new IllegalStateException("Hold expired; payment will be refunded"); return new Booking(UUID.randomUUID().toString(), showId, userId, List.copyOf(seatIds), total); }}Complexity and performance
Under one lock per show.
Hundreds.
Trade-offs
Long holds reduce payment failures from expiry but lock inventory; short holds free seats faster but frustrate slow users.
In-memory locks work for one server; multiple servers need database row locks, conditional updates, or Redis locks with expiry.
Variants and related techniques
UPDATE show_seats SET status='HELD' WHERE id IN (...) AND status='AVAILABLE' with a row count check.
Queue users during demand spikes (blockbuster releases).
Common mistakes
- Checking availability and booking in separate steps without locking.
Fix: Hold atomically for all seats.
- Holding seats forever when users abandon checkout.
Fix: Expiring holds with lazy or scheduled release.
- Partial holds (some seats taken).
Fix: All-or-nothing hold.
Interview questions
How do you prevent double booking?
Acquire an atomic, all-or-nothing hold on the selected show seats with an expiry (synchronized in-memory lock, database conditional update, or distributed lock), confirm only if the hold is still valid and owned by the user, and use unique constraints as a final safety net.
What happens if payment succeeds after the hold expired?
Confirmation fails because the hold is invalid; the system automatically refunds the payment, or extends holds while payment is in progress to make this rare.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Seat booking with holds and expiry | Medium | Concurrency. |
| Full BookMyShow with search, pricing, cancellation | Hard | Modeling. |