Definition
- Movie – title, genre, duration.
- Cinema – a venue with multiple
Screenobjects. - Screen – has a screen number and a fixed set of
Seatpositions. - Show – one screening of a Movie on a Screen at a time; holds the state of each seat (AVAILABLE, LOCKED, BOOKED).
- Booking – created when payment succeeds; references the Show and the booked seats.
- Seat locking – a two-phase mechanism (lock with TTL, then confirm) that prevents double-selling under heavy concurrency.
Booking a cinema seat is like reserving a specific numbered locker that thousands want at the same second. When Rajesh clicks seat A5 for the 9pm show, National Bank of India style systems place a short hold (lock) on A5 while he pays, so Priya cannot grab the same seat. If he does not pay in time, the hold expires and A5 is free again.
Understanding the Concepts
What are the core entities?
The core entities are Movie, Cinema, Screen, Seat, Show, SeatStatus, and Booking.
package com.bank.booking.model;
public enum SeatStatus {
AVAILABLE, LOCKED, BOOKED
}
package com.bank.booking.model;
public class Seat {
private final String seatId;
private SeatStatus status;
private String lockedByUserId;
private long lockExpiry;
public Seat(String seatId) {
this.seatId = seatId;
this.status = SeatStatus.AVAILABLE;
}
public synchronized boolean lock(String userId, long ttlMillis) {
if (status != SeatStatus.AVAILABLE) return false;
this.status = SeatStatus.LOCKED;
this.lockedByUserId = userId;
this.lockExpiry = System.currentTimeMillis() + ttlMillis;
return true;
}
public synchronized boolean confirm(String userId) {
if (status != SeatStatus.LOCKED
|| !userId.equals(lockedByUserId)) return false;
this.status = SeatStatus.BOOKED;
return true;
}
public synchronized void releaseIfExpired() {
if (status == SeatStatus.LOCKED
&& System.currentTimeMillis() > lockExpiry) {
this.status = SeatStatus.AVAILABLE;
this.lockedByUserId = null;
System.out.println("Seat " + seatId
+ " lock expired, released");
}
}
public String getSeatId() { return seatId; }
public SeatStatus getStatus() { return status; }
}
How does the two-phase lock-then-confirm work?
This is the central mechanism that every real booking platform uses.
1. User selects seats → seats transition to LOCKED with a short TTL (e.g., 5 minutes). 2. User completes payment within the TTL → seats transition to BOOKED. 3. If the TTL expires → a background sweeper returns seats to AVAILABLE.
package com.bank.booking.service;
import com.bank.booking.model.*;
import java.util.List;
public class BookingService {
private static final long LOCK_TTL = 5 * 60 * 1000; // 5 minutes
public boolean lockSeats(Show show, List<String> seatIds,
String userId) {
for (String seatId : seatIds) {
Seat seat = show.getSeat(seatId);
if (!seat.lock(userId, LOCK_TTL)) {
// Rollback any already-locked seats
seatIds.stream()
.map(show::getSeat)
.filter(s -> s.getStatus() == SeatStatus.LOCKED)
.forEach(Seat::releaseIfExpired);
return false;
}
}
System.out.println("Locked seats " + seatIds
+ " for user " + userId);
return true;
}
public Booking confirmBooking(Show show, List<String> seatIds,
String userId) {
for (String seatId : seatIds) {
if (!show.getSeat(seatId).confirm(userId)) {
throw new IllegalStateException(
"Cannot confirm seat " + seatId);
}
}
Booking booking = new Booking(
java.util.UUID.randomUUID().toString(),
show, seatIds, userId);
System.out.println("Booking confirmed: " + booking.getBookingId());
return booking;
}
}
Why must the lock be atomic?
Checking that a seat is available and setting it to locked must happen as one indivisible step. Otherwise two users both see AVAILABLE and both lock the same seat.
// The lock method is synchronized on the Seat object
public synchronized boolean lock(String userId, long ttlMillis) {
if (status != SeatStatus.AVAILABLE) return false;
this.status = SeatStatus.LOCKED;
// ...
}
In a distributed deployment, use a Redis lock keyed by show and seat with a TTL:
SET show:S1:seat:A5 userId NX PX 300000
The NX flag ensures the key is set only if it does not already exist (atomic), and PX 300000 sets a 5-minute expiry.
How does the background sweeper work?
A scheduled task periodically scans locked seats and releases any whose TTL has expired.
package com.bank.booking.service;
import com.bank.booking.model.Show;
public class LockSweeper implements Runnable {
private final Show show;
public LockSweeper(Show show) {
this.show = show;
}
@Override
public void run() {
show.getAllSeats().forEach(
seat -> seat.releaseIfExpired());
}
}
How does payment and cancellation work?
Payment goes through a payment strategy. Cancellation releases seats back to AVAILABLE.
public void cancel(Booking booking, Show show) {
for (String seatId : booking.getSeatIds()) {
show.getSeat(seatId).release();
}
System.out.println("Booking " + booking.getBookingId()
+ " cancelled, seats released");
}
What about the Show entity?
A Show is one screening of a Movie on a Screen at a specific time. It holds the state of each seat.
package com.bank.booking.model;
import java.time.LocalDateTime;
import java.util.*;
public class Show {
private final String showId;
private final Movie movie;
private final Screen screen;
private final LocalDateTime showTime;
private final Map<String, Seat> seats;
public Show(String showId, Movie movie, Screen screen,
LocalDateTime showTime, List<String> seatIds) {
this.showId = showId;
this.movie = movie;
this.screen = screen;
this.showTime = showTime;
this.seats = new LinkedHashMap<>();
for (String id : seatIds) {
seats.put(id, new Seat(id));
}
}
public Seat getSeat(String seatId) { return seats.get(seatId); }
public Collection<Seat> getAllSeats() { return seats.values(); }
}
What should a senior engineer know?
- The TTL on the lock is core correctness, not optional. Without it, abandoned selections permanently starve inventory.
- In a distributed system, Redis with NX + PX gives both atomicity and automatic expiry.
- The lock-then-confirm pattern is the same two-phase approach used in banking for hold-then-capture on credit cards.
- For popular shows, thousands of users contend for the same seats simultaneously, so the locking mechanism must be highly efficient.
What are common interview follow-up questions?
- How do you handle partial lock failure (some seats lock, one fails)?
- What happens if payment takes longer than the TTL?
- How would you implement seat selection UI showing real-time availability?
- How do you handle refunds on cancellation?
- How would you scale this to millions of concurrent users?
Visual Overview
Two-Phase Lock-Then-Confirm:
────────────────────────────
User selects A5, A6
│
▼
lockSeats([A5, A6], userId, TTL=5min)
│
▼
Seats: AVAILABLE → LOCKED (5 min TTL)
│
├── Payment succeeds within TTL
│ ▼
│ confirmBooking()
│ Seats: LOCKED → BOOKED
│
└── TTL expires (no payment)
▼
LockSweeper runs
Seats: LOCKED → AVAILABLE
Seat State Transitions:
───────────────────────
AVAILABLE ──lock──→ LOCKED ──confirm──→ BOOKED
▲ │
│ TTL expires
└─── release ─────┘
Key Takeaway
When you design a movie ticket booking system, treat concurrency as the first-class problem, because the entire difficulty is making sure a single seat is never sold to two people during a blockbuster rush. A Show is one screening of a Movie on a Screen at a time, and it tracks each seat as available, locked, or booked.
The two-phase approach that every real platform uses is lock then confirm. When a user selects seats, atomically move them from available to locked with a short expiry of a few minutes. If payment succeeds within that window, the seats become booked. If the window expires, a background sweeper returns the seats to available so an abandoned cart never freezes inventory.
The atomicity of the lock is everything. Checking that a seat is available and setting it to locked must happen as one indivisible step, otherwise two users both see available and both lock the same seat. In a single node, use a synchronised method. In a distributed deployment, use a Redis lock keyed by show and seat with a TTL, which gives both atomicity and automatic expiry.
The remaining pieces are straightforward: payment through a payment strategy, cancellation that releases seats back to available, and the show entity that ties a movie, screen, and time together. The senior insight is that the TTL on the lock is not optional detail but core correctness, because without it abandoned selections permanently starve inventory. At National Bank of India, this is the same discipline used when many clients contend for a single limited resource.
Remember one sentence: a movie booking system uses a two-phase lock-then-confirm with a TTL to atomically sell seats without double-booking under heavy concurrency.
---