Bird
Raised Fist0

Design a Movie Ticket Booking System - Seats, Concurrency Lock, Expiry, Payment

LLDmediumInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Movie Ticket Booking System - Seats, Concurrency Lock, Expiry, Payment
40 min ยท medium
medium

Design a movie ticket booking system for a multiplex with 5 seating levels and 4 entry gates. The system must handle seat availability, concurrent booking attempts, seat lock expiry after a timeout, and payment processing with status updates.

  • The multiplex has 5 levels of seating, each with a fixed number of seats.
  • There are 4 gates for entry, but seat booking is centralized via the system.
  • Seats can be locked for a user during booking to prevent double booking; locks expire after 5 minutes if payment is not completed.
  • Payment processing updates seat status atomically to CONFIRMED or releases the lock on failure.
  • Pricing varies by level and seat type (Regular, Premium, VIP).
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define Seat and SeatStatus with concurrency-safe state
Design the Seat class with state management for availability and locking. How will you represent seat status and ensure thread-safe updates?
๐Ÿ’ก Hint
Use enum for SeatStatus to avoid string typos. Use volatile or synchronized methods to manage seat state changes atomically.
enum SeatStatus { AVAILABLE, LOCKED, CONFIRMED } for type safety; Seat class uses private volatile SeatStatus status and public synchronized boolean lockSeat(String userId) to atomically check and set status.
Mid: Uses enum for seat status and synchronized methods for state changes. Senior: Explains how volatile ensures visibility and synchronized lockSeat prevents race conditions where two threads read AVAILABLE simultaneously and both lock the seat.
โš ๏ธCommon Wrong Answer
Using String for seat status leads to runtime bugs due to typos and no compile-time checks; not synchronizing lockSeat causes race conditions allowing double booking.
Step 2
Implement Seat Lock Expiry Mechanism
How will you implement seat lock expiry after 5 minutes if payment is not completed? Consider concurrency and lifecycle ownership.
๐Ÿ’ก Hint
Use a ScheduledExecutorService to schedule unlock tasks. Store lock timestamp and use synchronized methods to check expiry and release locks.
Use ScheduledExecutorService to schedule unlockSeat() after 5 minutes; seat lock timestamp stored as volatile long lockTimestamp; unlockSeat() synchronized to atomically update status and notify waiting threads.
Mid: Uses scheduled tasks for expiry. Senior: Explains how volatile lockTimestamp ensures visibility across threads and synchronized unlockSeat prevents race with payment thread confirming seat.
โš ๏ธCommon Wrong Answer
Using Timer without synchronization causes inconsistent seat states; not using volatile for lockTimestamp leads to stale reads and premature or delayed unlock.
Step 3
Design Payment Processing with Atomic Seat Status Update
How will payment processing update seat status atomically to CONFIRMED or release lock on failure? How to avoid inconsistent seat states?
๐Ÿ’ก Hint
Use synchronized method confirmSeatPayment(String seatId) that atomically checks seat is LOCKED and sets CONFIRMED; rollback unlock if payment fails.
PaymentService calls synchronized Seat.confirmPayment() which atomically sets status=CONFIRMED only if current status=LOCKED; else throws exception; rollback calls Seat.unlockSeat() synchronized method.
Mid: Payment updates seat status atomically. Senior: Explains thread interleaving where payment thread confirms seat while expiry thread tries to unlock; synchronized methods prevent inconsistent states.
โš ๏ธCommon Wrong Answer
Updating seat status without synchronization causes race conditions where seat can be both CONFIRMED and AVAILABLE; payment failure not rolling back lock leads to permanent seat lock.
Step 4
Model Theater with Levels and Seats Using Composition
How will you model the theater with 5 levels and seats per level? What relationship type and lifecycle ownership applies?
๐Ÿ’ก Hint
Use composition: TheaterLevel contains Seat objects; Theater contains TheaterLevel objects. Seats cannot exist independently of levels.
Theater has composition relationship with TheaterLevel; TheaterLevel has composition with Seat; lifecycle ownership means destroying Theater destroys all levels and seats.
Mid: Correct composition hierarchy. Senior: Explains lifecycle ownership prevents dangling seats or levels; clarifies why aggregation or association is inappropriate here.
โš ๏ธCommon Wrong Answer
Using aggregation or association for seats leads to seats existing without levels causing lifecycle management bugs and memory leaks.
Step 5
Handle Concurrent Booking Requests Across Multiple Gates
How will you handle concurrent booking requests from 4 gates to prevent double booking?
๐Ÿ’ก Hint
Use synchronized methods on Seat objects for locking; consider thread-safe collections for seat lookup; volatile for seat status visibility.
Each Seat exposes synchronized lockSeat() method; booking service uses ConcurrentHashMap<String, Seat> for seat lookup; volatile SeatStatus ensures visibility; no global lock to avoid bottleneck.
Mid: Uses synchronized on seat-level locking. Senior: Explains thread interleaving where two threads from different gates attempt lockSeat() concurrently; synchronized prevents both succeeding.
โš ๏ธCommon Wrong Answer
Using global lock on Theater causes performance bottleneck; no synchronization on seat allows double booking under concurrent requests.
๐Ÿ”Œ
API Design Challenge
Design APIs to support seat locking, unlocking, payment confirmation, and querying seat availability concurrently from multiple gates.
  • Use REST endpoints with idempotent operations for seat locking and unlocking.
  • Design APIs to handle concurrent requests safely using synchronized methods or locks.
  • Include payment status callbacks to update seat status atomically.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Use enum for SeatStatus instead of String
  • Seat status field declared volatile for visibility
  • Synchronized methods for atomic seat state changes
  • ScheduledExecutorService for seat lock expiry
  • Composition relationship for Theater -> TheaterLevel -> Seat with lifecycle ownership
Good to Have - 30%
  • ConcurrentHashMap for thread-safe seat lookup
  • Payment processing with synchronized confirmPayment() and rollback
  • Fine-grained locking at Seat level instead of global locks
  • Volatile lockTimestamp for expiry correctness
  • Clear separation of concerns between booking, payment, and expiry
Bonus - 10%
  • Explain exact thread interleaving causing double booking without synchronization
  • Use of AtomicReference or other concurrency primitives for seat status
โœ…
Model Answer
โ–ผ Reveal

This design uses Java concurrency primitives to ensure thread-safe seat booking with lock expiry and atomic payment confirmation. The system models theater composition with levels and seats, supports concurrent booking from multiple gates, and integrates payment processing with rollback on failure.

  • Use enum SeatStatus to prevent string typos and enable compile-time checks.
  • Declare seat status and lockTimestamp as volatile to ensure visibility across threads.
  • Synchronize lockSeat(), unlockSeat(), and confirmPayment() methods to provide atomic state changes and prevent race conditions.
  • Use ScheduledExecutorService to schedule unlockSeat() calls after 5 minutes to expire locks safely.
  • Model Theater -> TheaterLevel -> Seat with composition to enforce lifecycle ownership and prevent dangling references.
  • Use ConcurrentHashMap for seat lookup to allow concurrent access without global locks, improving scalability.
  • Avoid global locks; use fine-grained locking at Seat level to maximize concurrency and throughput.
  • Payment processing uses synchronized confirmPayment() to atomically update seat status and rollback on failure to maintain consistency.
  • Use enum SeatStatus for compile-time safety and IDE support over String constants.
  • Seat class uses private volatile SeatStatus status to ensure visibility of state changes across threads.
  • Synchronized methods like lockSeat() and unlockSeat() provide atomic check-and-set semantics to prevent race conditions.
  • ScheduledExecutorService schedules seat lock expiry tasks to release locks after timeout.
  • Payment processing uses synchronized confirmPayment() to atomically update seat status and rollback on failure.
  • Theater composition hierarchy: Theater -> TheaterLevel -> Seat with lifecycle ownership ensures proper object containment and destruction.
  • ConcurrentHashMap for seat lookup provides thread-safe access without global locking bottlenecks.
  • Avoid global locks; use fine-grained locking at Seat level to maximize concurrency.
  • Volatile fields ensure visibility of seat status and lock timestamps across threads.
  • Explicit lifecycle ownership clarifies object dependencies and prevents memory leaks.

Practice

(1/5)
1. You are designing a Vehicle abstraction for a parking system. Should Vehicle be an abstract class or an interface if you need to store state like licensePlate and provide default behavior for getSize()? Why?
easy
A. Use an interface, because it allows multiple inheritance and state can be stored externally.
B. Use an interface, because interfaces can hold state and default methods provide behavior.
C. Use an abstract class, because it can hold instance variables and provide default method implementations.
D. Use an abstract class, because interfaces cannot define any methods.

Solution

  1. Step 1: Analyze state and behavior requirements

    The design requires storing state (licensePlate) and providing default behavior (getSize()).
  2. Step 2: Choose correct OOP construct

    Abstract classes can have instance variables and concrete methods with default behavior. Interfaces in Java 8+ can have default methods but cannot hold instance state. Therefore, abstract class fits the need.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Abstract class supports state and default behavior [check]
Hint: State needs abstract class; interfaces lack instance variables [check]
Common Mistakes:
2. In a vending machine design, a State interface defines methods like selectProduct(), insertMoney(), and dispense(). Concrete classes like IdleState, HasMoneyState, and SoldState implement this interface. Which design pattern is applied here and why is it the correct choice?
easy
A. Observer pattern, because states notify the vending machine when transitions occur.
B. Strategy pattern, because it allows changing algorithms at runtime for product selection.
C. Decorator pattern, because it adds responsibilities to states dynamically without altering their interface.
D. State pattern, because it encapsulates state-specific behavior and transitions within state classes.

Solution

  1. Step 1: Identify the role of State interface and concrete states

    The State interface defines behavior that varies depending on the vending machine's current state. Concrete classes implement these behaviors and handle transitions.
  2. Step 2: Match pattern intent to scenario

    The State pattern encapsulates state-specific behavior and transitions, allowing the vending machine to change behavior dynamically without complex conditionals.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    State pattern encapsulates state behavior and transitions [check]
Hint: State pattern encapsulates behavior per state [check]
Common Mistakes:
3. In the Elevator System, should the Elevator class implement an interface or extend an abstract class to represent different elevator types (e.g., passenger, freight)? Consider that elevators share common state and behavior but also have type-specific logic.
easy
A. Use an abstract class, because elevators share common state and partial behavior implementations.
B. Use an interface, because elevators only share behavior signatures, not state.
C. Use a concrete class, because inheritance is unnecessary for elevator types.
D. Use multiple interfaces, one per behavior, to avoid state duplication.

Solution

  1. Step 1: Analyze shared state and behavior

    Elevators share common fields like currentFloor, direction, and doorStatus, plus some implemented methods.
  2. Step 2: Abstract class fits shared state and partial behavior

    Abstract class allows code reuse and enforces common state, while subclasses override type-specific logic.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Abstract class supports shared state and behavior [check]
Hint: Abstract class for shared state; interface for pure behavior [check]
Common Mistakes:
4. You want to add a WeekendBonusRule that gives players extra moves on weekends without modifying existing classes. Which SOLID principle guides the minimal change, and what is the correct approach?
medium
A. Single Responsibility Principle: add weekend logic inside the Player class.
B. Open/Closed Principle: create a new class implementing a MoveRule interface and inject it into the game engine.
C. Liskov Substitution Principle: subclass Player to WeekendPlayer with bonus logic.
D. Dependency Inversion Principle: make Player depend on concrete WeekendBonusRule.

Solution

  1. Step 1: Identify SOLID principle for extension without modification

    Open/Closed Principle (OCP) states software entities should be open for extension but closed for modification.
  2. Step 2: Evaluate approaches

    Subclassing Player to WeekendPlayer allows extension without modifying Player; injecting new MoveRule class also respects OCP but requires interface design.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Subclassing extends behavior without modifying base class [check]
Hint: Extend via subclass or interface, don't modify existing classes [check]
Common Mistakes:
5. The system needs to support ElectricCar and ChargingSpot without modifying existing Vehicle or Spot classes. What is the minimum change to support this extensibility in Java?
hard
A. Modify Vehicle class to include a boolean isElectric and add charging logic inside existing methods.
B. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update compatibility map externally.
C. Create a new ElectricVehicle interface and modify Spot to check for it explicitly.
D. Add charging logic inside Vehicle and Spot classes directly, breaking encapsulation.

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Enums can be extended with new constants; subclassing ElectricCar adds new behavior without changing Vehicle.
  2. Step 2: Update compatibility externally

    Updating SpotCompatibility.COMPATIBILITY_MAP externally respects OCP and avoids class explosion or god classes.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Extend enums + subclass + external map update -> minimal change [check]
Hint: Extend enums + subclass + external map -> minimal change [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard