Requirements & System Scope
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.
Class Diagram & Entity Relationships
Structural blueprint showing entities and reservation states:
Loading...
- Fine-Grained Concurrency Lock: Each
Seatcontains a dedicated mutex lock, allowing localized concurrency checks without locking the entire theater or show. - Strategy Encapsulation: decoupled pricing strategy processes amounts based on seat categories and dates.
Design Patterns & SOLID Principles
- Strategy Pattern (Dynamic Pricing): Implemented via
PricingStrategyinterface and concreteDynamicPricingStrategyto allow easy adjustment of price calculators without modifying core models. - State Pattern (Booking State Machine): Booking states (PENDING, CONFIRMED, EXPIRED, CANCELLED) transition predictably, ensuring seat state operations (reserve, book, release) match the transaction state flow.
- Deadlock Prevention via Lock Ordering: Avoids deadlocks by sorting the requested seat IDs alphabetically prior to acquiring locks. This guarantees that concurrent threads requesting overlapping seats acquire locks in the exact same sequence.
Core Execution Workflows
Two-Phase Booking Pipeline
- Client invokes
createBooking(show, seatIds). - System retrieves target seats. To avoid deadlocks, it sorts the seats by ID.
- System attempts to lock and reserve each seat in order:
- If the seat is AVAILABLE (or its reservation expired), transition to RESERVED and set
reservationTime = now. - If any seat fails reservation, trigger an immediate rollback: release all seats acquired in this transaction.
- If the seat is AVAILABLE (or its reservation expired), transition to RESERVED and set
- Generate a temporary
Bookinginstance with statusPENDING. - On payment checkout success, call
confirm()on the booking to transition seats toBOOKED. - On payment timeout or cancellation, call
cancel(EXPIRED/CANCELLED)to release all held seats back toAVAILABLE.
Concurrency & Thread Safety Strategy
In high-demand ticket sales, multiple threads seek to book overlapping seat selections simultaneously:
- Fine-Grained Seat Locks: Individual reentrant locks protect status changes at the seat level, keeping throughput high compared to synchronizing the global service.
- Deterministic Lock Sorting: Sorting requested seat IDs (e.g. S1 before S2) eliminates circular wait conditions, making the system 100% deadlock-proof under concurrent requests.
- Concurrent HashMaps: Store booking references safely to handle thread-safe insertions and quick reads.
Complete Clean Code Blueprint
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();
}
}Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...