Based on the requirements and use cases, identify the main objects of the system and analyze how they interact and relate to each other...
showtimes: movie name, time, theather
theater meta: theaterName, seats per theater
booking: userId, bookingId, timestamp
For each class, define the attributes (data) it will hold and the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation. Write your code in the code editor below.
☕Java🐍PythonJavaScriptTypeScriptC++
Seat
from __future__ import annotations
from enum import Enum
class SeatType(Enum):
# Define seat types here
REGULAR = 'Regular'
VIP = 'VIP'
class Seat:
def init(self, seat_id: str, row: str, number: int, seat_type: SeatType):
self._seat_id = seat_id
self._row = row
self._number = number
self._seat_type = seat_type
@property
def seat_id(self) -> str:
return self._seat_id
@property
def row(self) -> str:
return self._row
@property
def number(self) -> int:
return self._number
@property
def seat_type(self) -> SeatType:
return self._seat_type
def __eq__(self, other: object) -> bool:
if not isinstance(other, Seat):
return False
return self._seat_id == other._seat_id
def __hash__(self) -> int:
return hash(self._seat_id)
Seat is intentionally minimal: row, number, and type. It carries no availability state because availability is per-show, not per-seat. This immutability makes Seat safe to share across every Show on the same Screen without defensive copies.
☕Java🐍PythonJavaScriptTypeScriptC++
Movie
class Movie:
def init(self, movie_id: str, title: str, duration_minutes: int, genre: str, rating: str):
self._movie_id = movie_id
self._title = title
self._duration_minutes = duration_minutes
self._genre = genre
self._rating = rating
@property
def movie_id(self) -> str:
return self._movie_id
@property
def title(self) -> str:
return self._title
Movie is also a simple data holder. The interesting logic lives downstream in Show, where the movie's duration determines scheduling constraints but does not affect booking mechanics.
☕Java🐍PythonJavaScriptTypeScriptC++
Show
from future import annotations
from typing import List, Dict
from threading import Lock
from datetime import datetime
class Show:
def init(self, show_id: str, movie: Movie, screen: Screen, start_time: datetime):
self.show_id = show_id
self.movie = movie
self.screen = screen
self.start_time = start_time
self.seat_availability: Dict[Seat, SeatStatus] = {seat: SeatStatus.AVAILABLE for seat in screen.get_seats()}
self._lock = Lock()
def get_available_seats(self) -> List[Seat]:
with self._lock:
return [seat for seat, status in self.seat_availability.items() if status == SeatStatus.AVAILABLE]
def lock_seats(self, seats: List[Seat]) -> bool:
with self._lock:
if any(self.seat_availability[seat] != SeatStatus.AVAILABLE for seat in seats):
return False
for seat in seats:
self.seat_availability[seat] = SeatStatus.HELD
return True
def confirm_seats(self, seats: List[Seat]) -> None:
with self._lock:
for seat in seats:
self.seat_availability[seat] = SeatStatus.BOOKED
def release_seats(self, seats: List[Seat]) -> None:
with self._lock:
for seat in seats:
if self.seat_availability[seat] != SeatStatus.BOOKED:
self.seat_availability[seat] = SeatStatus.AVAILABLE
Notice how lockSeats() uses a two-pass approach: first verify all seats are AVAILABLE, then set them all to HELD. If any check fails, none are modified. This atomic check-and-set prevents partial locking. The synchronized block on the Show's lock object ensures that only one thread can evaluate and modify seat status at a time for this particular show.
Booking ties together the Show, selected Seats, and Payment into a single reservation record. The status field enforces a strict lifecycle: PENDING allows confirm() or expire(), CONFIRMED allows cancel(), and terminal states reject all mutations.
☕Java🐍PythonJavaScriptTypeScriptC++
Booking
from future import annotations
from datetime import datetime, timedelta
from threading import RLock
from typing import List
class Booking:
HOLD_TIMEOUT_MINUTES = 10
def __init__(self, booking_id: str, show: Show, seats: List[Seat], user_id: str, pricing_strategy: PricingStrategy):
self.booking_id = booking_id
self.show = show
self.seats = seats.copy()
self.user_id = user_id
self.status = BookingStatus.PENDING
self.created_at = datetime.now()
self.total_amount = sum(pricing_strategy.calculate_price(seat, show) for seat in seats)
self.payment = None
self._lock = RLock()
def confirm(self, payment: Payment) -> bool:
with self._lock:
if self.status != BookingStatus.PENDING:
return False
if payment.status != PaymentStatus.SUCCESS:
return False
self.payment = payment
self.status = BookingStatus.CONFIRMED
self.show.confirm_seats(self.seats)
return True
def cancel(self) -> bool:
with self._lock:
if self.status != BookingStatus.CONFIRMED:
return False
self.status = BookingStatus.CANCELLED
self.show.release_seats(self.seats)
if self.payment:
self.payment.refund()
return True
def is_expired(self) -> bool:
return self.status == BookingStatus.PENDING and datetime.now() > self.created_at + timedelta(minutes=self.HOLD_TIMEOUT_MINUTES)
def expire(self):
with self._lock:
if self.is_expired():
self.status = BookingStatus.EXPIRED
self.show.release_seats(self.seats)
RegularPricing maps each SeatType to a base price. WeekendPricing wraps any base strategy and applies a multiplier. This Decorator approach means you never edit existing pricing code to add new rules.
☕Java🐍PythonJavaScriptTypeScriptC++
RegularPricing
from future import annotations
from abc import ABC, abstractmethod
from enum import Enum
from datetime import datetime
class SeatType(Enum):
REGULAR = 'REGULAR'
PREMIUM = 'PREMIUM'
VIP = 'VIP'
class Seat:
def get_seat_type(self) -> SeatType:
pass
class Show:
def get_start_time(self) -> datetime:
pass
class PricingStrategy(ABC):
@abstractmethod
def calculate_price(self, seat: Seat, show: Show) -> float:
pass
class RegularPricing(PricingStrategy):
BASE_PRICES = {
SeatType.REGULAR: 10.0,
from future import annotations
from typing import List, Dict
from threading import Lock
from datetime import datetime
class Show:
def init(self, show_id: str, movie: Movie, screen: Screen, start_time: datetime):
self.show_id = show_id
self.movie = movie
self.screen = screen
self.start_time = start_time
self.seat_availability: Dict[Seat, SeatStatus] = {seat: SeatStatus.AVAILABLE for seat in screen.get_seats()}
self._lock = Lock()
def get_available_seats(self) -> List[Seat]:
with self._lock:
return [seat for seat, status in self.seat_availability.items() if status == SeatStatus.AVAILABLE]
def lock_seats(self, seats: List[Seat]) -> bool:
with self._lock:
if any(self.seat_availability[seat] != SeatStatus.AVAILABLE for seat in seats):
return False
for seat in seats:
self.seat_availability[seat] = SeatStatus.HELD
return True
def confirm_seats(self, seats: List[Seat]) -> None:
with self._lock:
for seat in seats:
self.seat_availability[seat] = SeatStatus.BOOKED
def release_seats(self, seats: List[Seat]) -> None:
with self._lock:
for seat in seats:
if self.seat_availability[seat] != SeatStatus.BOOKED:
self.seat_availability[seat] = SeatStatus.AVAILABLE
from future import annotations
from datetime import datetime, timedelta
from threading import RLock
from typing import List
class Booking:
HOLD_TIMEOUT_MINUTES = 10
def __init__(self, booking_id: str, show: Show, seats: List[Seat], user_id: str, pricing_strategy: PricingStrategy):
self.booking_id = booking_id
self.show = show
self.seats = seats.copy()
self.user_id = user_id
self.status = BookingStatus.PENDING
self.created_at = datetime.now()
self.total_amount = sum(pricing_strategy.calculate_price(seat, show) for seat in seats)
self.payment = None
self._lock = RLock()
def confirm(self, payment: Payment) -> bool:
with self._lock:
if self.status != BookingStatus.PENDING:
return False
if payment.status != PaymentStatus.SUCCESS:
return False
self.payment = payment
self.status = BookingStatus.CONFIRMED
self.show.confirm_seats(self.seats)
return True
def cancel(self) -> bool:
with self._lock:
if self.status != BookingStatus.CONFIRMED:
return False
self.status = BookingStatus.CANCELLED
self.show.release_seats(self.seats)
if self.payment:
self.payment.refund()
return True
def is_expired(self) -> bool:
return self.status == BookingStatus.PENDING and datetime.now() > self.created_at + timedelta(minutes=self.HOLD_TIMEOUT_MINUTES)
def expire(self):
with self._lock:
if self.is_expired():
self.status = BookingStatus.EXPIRED
self.show.release_seats(self.seats)
Explain design tradeoffs you considered. Check and explain whether your design adheres to SOLID principles. Explain how your design can handle changes in scale and whether it would be easy to extend with new functionalities. Identify areas for future improvement...
Without the Strategy pattern, Booking calculates prices with conditionals:
java
// Without Strategy - conditional pricing
double price = BASE_PRICES.get(seat.getSeatType());
if (isWeekend(show)) price *= 1.3;
if (isHoliday(show)) price *= 1.5;
if (hasLoyaltyCard(user)) price *= 0.9;
Three pricing rules means three if-statements. Adding a fourth means editing Booking. The Strategy pattern extracts each rule into its own class:
java
// With Strategy - clean delegation
double price = pricingStrategy.calculatePrice(seat, show);
Booking delegates to whichever strategy is injected. Adding HolidayPricing means one new class and zero changes to Booking.
Booking follows a strict lifecycle: PENDING, CONFIRMED, CANCELLED, EXPIRED. The confirm() method only works from PENDING. cancel() only works from CONFIRMED. Rather than scattering if-checks across methods, each status defines which operations are valid. Although the solution uses enum-based checks for simplicity, a full State pattern would extract each status into its own class.
Show.lockSeats() is the critical operation. It must atomically check that all requested seats are AVAILABLE and then set them to HELD. If any seat fails the check, none are modified. This atomic check-and-set runs inside a synchronized block on the Show's internal lock object.
Key Insight
The lock granularity is per-Show. Users booking seats for different shows never contend with each other. Only users competing for the same show at the same instant serialize. This keeps the contention window narrow.
Key Insight
Why not use per-Seat locks instead of per-Show locks? Because users select multiple seats together (A5 and A6). Per-seat locks would require acquiring two locks in the correct order to avoid deadlocks, and a failed second lock means releasing the first and retrying. A single per-Show lock makes the multi-seat check-and-set atomic with no deadlock risk.
Why does the Decorator pattern fit pricing better than inheritance? Consider three pricing rules: weekend, holiday, and loyalty. With inheritance you need seven subclasses to cover every combination. With decorators, you create three wrapper classes and stack them at runtime. The number of classes grows linearly instead of exponentially.
Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Below is the class diagram for the movie ticket booking system.
owns
owns
schedules
screens
uses
tracks availability
references
reserves
has
uses
implements
implements
wraps
Cinema
-String cinemaId
-String name
-List<Screen> screens
-List<Show> shows
+addScreen(Screen)
+addShow(Show)
+getShows(Movie, LocalDate) : List<Show>
Screen
-String screenId
-String name
-List<Seat> seats
-int totalCapacity
+getSeats() : List<Seat>
Movie
-String movieId
-String title
-int durationMinutes
-String genre
Show
-String showId
-Movie movie
-Screen screen
-LocalDateTime startTime
-Map<Seat-SeatStatus> seatAvailability
+getAvailableSeats() : List<Seat>
+lockSeats(List<Seat>) : boolean
+confirmSeats(List<Seat>)
+releaseSeats(List<Seat>)
Seat
-String seatId
-String row
-int number
-SeatType seatType
Booking
-String bookingId
-Show show
-List<Seat> seats
-BookingStatus status
-double totalAmount
+confirm(Payment) : boolean
+cancel() : boolean
+expire()
Payment
-String paymentId
-double amount
-PaymentStatus status
+process() : boolean
+refund()
«interface»
PricingStrategy
+calculatePrice(Seat, Show) : double
RegularPricing
+calculatePrice(Seat, Show) : double
WeekendPricing
-PricingStrategy baseStrategy
+calculatePrice(Seat, Show) : double
Key points to highlight in an interview: