Airline Management System ā Complete Design Guide
Flight scheduling, seat inventory management, booking lifecycle, overbooking prevention, and dynamic pricing.
Scale: 500-1,000+ concurrent users, 1M+ flights/day, 99.9% uptime
Duration: 75-minute interview guide
Focus: Seat hold/confirm lifecycle, overbooking prevention, dynamic pricing, event notifications
Table of Contents
- Quick Start (5 min)
- Step 01: The Setup ā Clarify Requirements
- Step 02: Structure ā Define Entities
- Step 03: Interface ā APIs & Entry Points
- Step 04: Architecture ā Relationships & Diagram
- Step 05: Optimization ā Design Patterns
- Step 06: Implementation ā Code & Concurrency
- Demo Scenarios
- Interview Q&A
- Scaling Q&A
- Success Checklist
Quick Start
5-Minute Overview for Last-Minute Prep
What Problem Are We Solving?
Customers search flights ā hold seat (temporary 5-min reservation) ā pay ā confirm booking ā or hold expires ā seat released. Prevent overbooking through atomic seat status transitions and hold timeout management.
Core Flow
Browse Flights ā Select Seat ā HOLD (5 min) ā PAYMENT ā CONFIRM (permanent)
ā or timeout
EXPIRED (seat released)
Step 01: The Setup ā Clarify Requirements
Interview Tip: Never code immediately. Ask clarifying questions first. Define scope, actors, and constraints.
Questions to Ask (30 seconds each)
- Single machine or distributed? ā "Distributed system with 500-1K concurrent users"
- Do we track passenger history? ā "Yes, past bookings for cancellations"
- Multi-airline or single? ā "Single airline"
- Real payment processing? ā "Mock payment service for interview"
- Cancellations after confirmation? ā "Yes, with refund policies"
Actors (Who uses the system?)
| Actor | Role | Example Actions |
|---|---|---|
| Customer | Browse & book flights | Search, hold seat, confirm, cancel |
| Airline Admin | Manage flights & pricing | Create flights, set pricing strategy, monitor overbooking |
| System | Controller & notifications | Expire holds, send emails, update seat status |
Functional Requirements (What does the system do?)
ā Search & Browse - Search flights by route, date, aircraft type - View seat inventory with real-time availability
ā Hold & Reserve - Temporarily hold seat for 5 minutes - Prevent double-booking of same seat - Generate booking ID & hold expiry time
ā Confirm & Book - Confirm booking after payment - Permanently mark seat as booked - Update booking status
ā Cancel & Release - Cancel confirmed booking - Release seat back to available - Calculate refund based on policy
ā Automatic Expiry - Automatically expire holds after 5 minutes - Release seat if hold not confirmed - Notify passenger of expiry
ā Dynamic Pricing - Support multiple pricing strategies - Adjust price based on occupancy - Apply seat class surcharges (Economy vs Business)
ā Notifications - Notify on hold success, confirmation, expiry, cancellation - Support multiple channels (email, SMS, console)
Non-Functional Requirements (How does it perform?)
ā
Concurrency: Support 500-1000+ simultaneous holds/confirms
ā
Consistency: No overbooking (seat can only be held by ONE user)
ā
Availability: Real-time seat status updates, <100ms searches
ā
Latency: <200ms for hold/confirm operations
ā
Uptime: 99.9% (8.6 hours downtime/month allowed)
ā
Throughput: 1M flights/day = ~12 flights/second average (easily handled)
ā
Hold Expiry Accuracy: ±30 seconds tolerance acceptable
Constraints & Clarifications
| Constraint | Decision |
|---|---|
| Distributed? | YES - multi-region with replicas |
| Single airline? | YES - simplify scope |
| Real payment? | NO - mock service (payment failure still tested) |
| Overbooking allowed? | YES - controlled 5% oversell for no-shows |
| Cancellation fee? | Time-based: Full refund if >24h, 50% if >6h, None if <6h |
| Seat hold timeout | Fixed 5 minutes |
| Max passengers per booking | 1 (single seat per booking) |
Step 02: Structure ā Define Entities
Interview Tip: Extract core objects from requirements. Look for nouns. Write them on whiteboard immediately.
Step 2.1: List Core Entities (Extract Nouns)
From the requirements above, identify nouns:
Flight, Seat, Passenger, Booking, Price, Status, Notification, ...
Step 2.2: Define Core Classes
Seat ā A single seat in a flight
Properties:
- seat_id: str (e.g., "1A", "1B", "12C")
- seat_class: SeatClass (ECONOMY or BUSINESS)
- status: SeatStatus (AVAILABLE, HOLD, BOOKED)
- booked_by: Optional[str] (passenger ID or None)
Behaviors:
- is_available(): Check if seat can be held
- hold(): Transition to HOLD status
- book(): Transition to BOOKED status
- release(): Transition back to AVAILABLE
Flight ā A single flight with multiple seats
Properties:
- flight_id: str (e.g., "AA101")
- origin: str (e.g., "NYC")
- destination: str (e.g., "LAX")
- departure: datetime
- aircraft_type: str (e.g., "Boeing 737")
- seats: Dict[str, Seat] (collection of 30-180 seats)
Behaviors:
- add_seat(seat): Register seat in flight
- get_seat(seat_id): Retrieve seat by ID
- available_seats_count(): Count available seats for pricing
Passenger ā A person booking flights
Properties:
- passenger_id: str
- name: str
- email: str
Behaviors:
- (none - just data holder)
Booking ā A single seat reservation
Properties:
- booking_id: str (unique per booking)
- passenger: Passenger (who booked)
- flight: Flight (which flight)
- seat: Seat (which seat)
- price: float (calculated price)
- status: BookingStatus (HOLD, CONFIRMED, CANCELLED, EXPIRED)
- created_at: datetime
- hold_until: datetime (when hold expires)
Behaviors:
- confirm(): Mark as CONFIRMED, book the seat
- cancel(): Mark as CANCELLED, release seat
- expire(): Mark as EXPIRED, release seat (auto-called after 5 min)
AirlineSystem ā Main controller (Singleton)
Properties:
- flights: Dict[str, Flight]
- passengers: Dict[str, Passenger]
- bookings: Dict[str, Booking]
- observers: List[Observer] (for notifications)
- pricing_strategy: PricingStrategy
Behaviors:
- hold_seat(passenger_id, flight_id, seat_id): Create booking + hold seat
- confirm_booking(booking_id): Transition HOLD ā CONFIRMED
- cancel_booking(booking_id): Transition to CANCELLED
- check_and_expire_holds(): Expire all old HOLD bookings (background job)
- set_pricing_strategy(strategy): Switch pricing algorithm
- notify_observers(event, booking): Broadcast event to all listeners
Step 2.3: Define Enumerations (State & Type)
class SeatStatus(Enum):
AVAILABLE = "available" # Can be held
HOLD = "hold" # Temporarily reserved by user
BOOKED = "booked" # Permanently booked after payment
class BookingStatus(Enum):
HOLD = "hold" # User in checkout (5-min window)
CONFIRMED = "confirmed" # User paid, booking permanent
CANCELLED = "cancelled" # User cancelled
EXPIRED = "expired" # 5-min hold window closed
class SeatClass(Enum):
ECONOMY = 1 # $100 base price
BUSINESS = 2 # $200 base price
Step 2.4: Why These Entities?
| Entity | Why | Cost of Missing |
|---|---|---|
| Seat | Granular seat tracking | Can't prevent overbooking |
| Flight | Group seats logically | Can't organize inventory |
| Passenger | Track who booked | Can't send notifications |
| Booking | Hold lifecycle tracking | Can't manage holds or expiry |
| AirlineSystem | Central controller | No thread-safe coordination |
Step 03: Interface ā APIs & Entry Points
Interview Tip: Define the contract (inputs, outputs, exceptions) BEFORE implementation. Focus on "what" not "how".
Step 3.1: Public API Contracts
1. Search Flights
def search_flights(origin: str, destination: str, date: str) -> List[Flight]:
"""
Find all flights matching route and date.
Returns: List of Flight objects with available seat counts.
Raises: FlightNotFoundError if no matches.
Response Time: <100ms (cached)
"""
pass
2. Browse Seats
def get_available_seats(flight_id: str) -> Dict[str, Seat]:
"""
Get all available (AVAILABLE status) seats in a flight.
Returns: Dict mapping seat_id ā Seat object.
Raises: FlightNotFoundError if flight doesn't exist.
Response Time: <50ms (cached)
"""
pass
3. Hold Seat ā CRITICAL
def hold_seat(passenger_id: str, flight_id: str, seat_id: str,
hold_seconds: int = 300) -> Booking:
"""
Temporarily reserve a seat for 5 minutes (default 300 seconds).
Precondition: seat.status == AVAILABLE
Postcondition: seat.status == HOLD, booking.status == HOLD
Returns: Booking object with booking_id, price, hold_until timestamp.
Raises:
- SeatNotAvailableError: Seat already held/booked by another user
- FlightNotFoundError: Flight doesn't exist
- PassengerNotFoundError: Passenger not registered
- SeatNotFoundError: Seat ID invalid
Concurrency: THREAD-SAFE with atomic status transition
Response Time: <200ms
Idempotency: NO (multiple calls = multiple bookings)
"""
pass
4. Confirm Booking ā CRITICAL
def confirm_booking(booking_id: str) -> bool:
"""
Permanently book a seat after payment succeeds.
Precondition: booking.status == HOLD
Postcondition: booking.status == CONFIRMED, seat.status == BOOKED
Returns: True if success, False if failed.
Raises:
- BookingNotFoundError: Booking ID invalid
- BookingExpiredError: Hold window closed (now > hold_until)
- BookingNotHeldError: Booking not in HOLD state (already confirmed/cancelled)
Concurrency: THREAD-SAFE
Response Time: <200ms
Side Effects: Sends notification to passenger (email/SMS)
"""
pass
5. Cancel Booking
def cancel_booking(booking_id: str) -> bool:
"""
Cancel a booking and release seat back to available.
Precondition: booking.status in [HOLD, CONFIRMED]
Postcondition: booking.status = CANCELLED, seat.status = AVAILABLE
Returns: True if success, False if failed.
Raises:
- BookingNotFoundError: Booking ID invalid
- BookingAlreadyExpiredError: Can't cancel expired booking
Side Effects:
- Calculates refund based on time_until_departure
- Sends cancellation email
"""
pass
6. Automatic Hold Expiry (Background Job)
def check_and_expire_holds() -> None:
"""
Scan all HOLD bookings and auto-expire stale ones.
Called every 1 minute by background scheduler.
For each HOLD booking:
if now > booking.hold_until:
- Set booking.status = EXPIRED
- Set seat.status = AVAILABLE
- Notify passenger
Response Time: O(n) where n = total HOLD bookings (tolerate ~5 min delay)
Concurrency: Can run in parallel, guarded by distributed locks
"""
pass
7. Set Pricing Strategy
def set_pricing_strategy(strategy: PricingStrategy) -> None:
"""
Dynamically switch pricing algorithm.
New pricing applies to next hold_seat() call.
Strategy: Pluggable (FixedPricing or DemandBasedPricing)
"""
pass
8. Register Observer (For Notifications)
def add_observer(observer: Observer) -> None:
"""
Register callback for booking events.
Events: "held", "confirmed", "cancelled", "expired"
Observer will be called: observer.update(event, booking)
Example: Add EmailNotifier to send emails on confirm/expire
"""
pass
Step 3.2: Exception Hierarchy
class AirlineException(Exception):
"""Base exception"""
pass
class SeatNotAvailableError(AirlineException):
"""Seat already held/booked"""
pass
class BookingExpiredError(AirlineException):
"""Hold window closed"""
pass
class FlightNotFoundError(AirlineException):
"""Flight ID invalid"""
pass
class PassengerNotFoundError(AirlineException):
"""Passenger not registered"""
pass
Step 3.3: API Usage Example
system = AirlineSystem.get_instance()
# 1. Search flights
flights = system.search_flights("NYC", "LAX", "2025-01-15")
# 2. Browse seats
flight = flights[0]
seats = system.get_available_seats(flight.flight_id)
# 3. HOLD seat
booking = system.hold_seat(
passenger_id="P001",
flight_id="AA101",
seat_id="1A"
)
print(f"Booking ID: {booking.booking_id}, Price: ${booking.price}")
# 4. CONFIRM booking
success = system.confirm_booking(booking.booking_id)
# 5. CANCEL booking
system.cancel_booking(booking.booking_id)
Step 04: Architecture ā Relationships & Diagram
Interview Tip: Use composition, aggregation, and inheritance. Prefer composition over inheritance. Check cardinality (1:1, 1:N).
Step 4.1: Relationship Types
AirlineSystem HAS-A flights (1:N Composition)
āā AirlineSystem is "owner", manages lifecycle of flights
Flight HAS-A seats (1:N Composition)
āā Flight contains collection of Seat objects
Booking REFERENCES passenger (1:1 Association)
āā Booking links to Passenger (no ownership)
Booking REFERENCES flight (1:1 Association)
āā Booking links to Flight (no ownership)
Booking REFERENCES seat (1:1 Association)
āā Booking links to Seat (no ownership)
AirlineSystem USES-A PricingStrategy (1:1 Composition)
āā AirlineSystem owns and manages pricing algorithm
AirlineSystem NOTIFIES Observer (1:N Association)
āā Multiple observers listen to events
Step 4.2: Complete UML Class Diagram
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā AirlineSystem (Singleton) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā - _instance: AirlineSystem ā
ā - flights: Dict[str, Flight] ā
ā - passengers: Dict[str, Passenger] ā
ā - bookings: Dict[str, Booking] ā
ā - observers: List[Observer] ā
ā - pricing_strategy: PricingStrategy ā
ā - _lock: threading.Lock ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā + get_instance(): AirlineSystem ā
ā + hold_seat(...): Booking ā
ā + confirm_booking(booking_id): bool ā
ā + cancel_booking(booking_id): bool ā
ā + check_and_expire_holds(): void ā
ā + set_pricing_strategy(strategy) ā
ā + add_observer(observer): void ā
ā + notify_observers(event, booking) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā manages 1:N
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Flight ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā - flight_id: str ā
ā - origin: str ā
ā - destination: str ā
ā - departure: datetime ā
ā - aircraft_type: str ā
ā - seats: Dict[str, Seat] ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā + add_seat(seat): void ā
ā + get_seat(seat_id): Seat ā
ā + available_seats_count(): int ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā contains 1:N
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Seat ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā - seat_id: str ā
ā - seat_class: SeatClass ā
ā - status: SeatStatus ā
ā - booked_by: Optional[str] ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā + is_available(): bool ā
ā + hold(): void ā
ā + book(): void ā
ā + release(): void ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā² linked by
ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Booking ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā - booking_id: str ā
ā - passenger: Passenger ā
ā - flight: Flight ā
ā - seat: Seat ā
ā - price: float ā
ā - status: BookingStatus ā
ā - hold_until: datetime ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā + confirm(): void ā
ā + cancel(): void ā
ā + expire(): void ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā references 1:1
ā¼
āāāāāāāāāāāāāāāāāāāāāāā
ā Passenger ā
āāāāāāāāāāāāāāāāāāāāāāā¤
ā - passenger_id: str ā
ā - name: str ā
ā - email: str ā
āāāāāāāāāāāāāāāāāāāāāāā
STRATEGY PATTERN (Pricing):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PricingStrategy (Abstract) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā + calculate_price(flight, seat) ā
āāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā implemented by
āāā FixedPricing (Economy $100, Business $200)
āāā DemandBasedPricing (1.0x - 1.5x multiplier)
OBSERVER PATTERN (Notifications):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Observer (Abstract) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā + update(event, booking) ā
āāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā implemented by
āāā ConsoleObserver (logging)
āāā EmailNotifier
āāā SMSNotifier
Step 4.3: Cardinality Summary
| Relationship | Cardinality | Type | Reason |
|---|---|---|---|
| AirlineSystem ā Flights | 1:N | Composition | System owns all flights |
| Flight ā Seats | 1:N | Composition | Flight owns all its seats |
| Booking ā Passenger | 1:1 | Association | Booking references one passenger |
| Booking ā Flight | 1:1 | Association | Booking references one flight |
| Booking ā Seat | 1:1 | Association | Booking reserves one seat |
| AirlineSystem ā PricingStrategy | 1:1 | Composition | System owns pricing rule |
| AirlineSystem ā Observers | 1:N | Association | System notifies multiple listeners |
Step 05: Optimization ā Design Patterns
Interview Tip: Don't force patterns. Only solve specific problems.
Pattern 1: Singleton (For AirlineSystem)
Problem: Multiple threads need single consistent view of flights, bookings, passengers.
Solution: One global AirlineSystem instance, thread-safe initialization.
class AirlineSystem:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Benefit: ā
Single source of truth, ā
Thread-safe (double-checked lock), ā
Global access
Trade-off: ā ļø Global state (hard to test), ā ļø Harder to scale across machines
Pattern 2: Strategy (For Pricing)
Problem: Pricing varies (fixed vs demand-based) and may change in future.
Solution: Pluggable pricing algorithms, swap without modifying booking logic.
class PricingStrategy(ABC):
@abstractmethod
def calculate_price(self, flight: Flight, seat: Seat) -> float:
pass
class FixedPricing(PricingStrategy):
def calculate_price(self, flight: Flight, seat: Seat) -> float:
return 200.0 if seat.seat_class == SeatClass.BUSINESS else 100.0
class DemandBasedPricing(PricingStrategy):
def calculate_price(self, flight: Flight, seat: Seat) -> float:
base = 200.0 if seat.seat_class == SeatClass.BUSINESS else 100.0
occupancy_rate = 1.0 - (flight.available_seats_count() / len(flight.seats))
multiplier = 1.0 + (occupancy_rate * 0.5) # up to 1.5x surge
return base * multiplier
# Usage: Switch algorithm at runtime
system.set_pricing_strategy(DemandBasedPricing())
price = system.pricing_strategy.calculate_price(flight, seat)
Benefit: ā
Easy to add new pricing (Seasonal, EarlyBird, etc.), ā
No booking logic change
Trade-off: ā ļø Extra abstraction layer
Pattern 3: Observer (For Notifications)
Problem: Booking events (hold, confirm, expire) need to trigger emails/SMS/logging.
Solution: Observer pattern decouples event producer from consumers.
class Observer(ABC):
@abstractmethod
def update(self, event: str, booking: Booking):
pass
class EmailNotifier(Observer):
def update(self, event: str, booking: Booking):
if event == "confirmed":
send_email(booking.passenger.email, "Booking Confirmed!")
class ConsoleObserver(Observer):
def update(self, event: str, booking: Booking):
print(f"[{event.upper()}] Booking {booking.booking_id}")
# Usage: Add multiple observers
system.add_observer(EmailNotifier())
system.add_observer(ConsoleObserver())
# On any event:
system.notify_observers("confirmed", booking)
Benefit: ā
Loose coupling, ā
Easy to add new notifiers (SMS, Slack, etc.)
Trade-off: ā ļø Observer lifecycle management
Pattern 4: State Enums (For Status Transitions)
Problem: Booking can be in HOLD, CONFIRMED, EXPIRED, CANCELLED. Invalid transitions must be prevented.
Solution: Use enums to explicitly track state.
class BookingStatus(Enum):
HOLD = "hold"
CONFIRMED = "confirmed"
EXPIRED = "expired"
CANCELLED = "cancelled"
# Valid transitions:
def confirm_booking(booking_id: str):
booking = bookings[booking_id]
if booking.status != BookingStatus.HOLD:
raise ValueError("Can only confirm HOLD bookings")
booking.status = BookingStatus.CONFIRMED
Benefit: ā
Explicit state, ā
Invalid transitions caught at runtime
Trade-off: ā ļø Need explicit state transition logic
Pattern 5: Factory (For Creating Bookings)
Problem: Creating a booking requires initializing multiple objects (Seat, Booking, notification).
Solution: Factory method encapsulates creation logic.
def hold_seat(self, passenger_id: str, flight_id: str, seat_id: str):
# Factory logic: create booking with all dependencies
seat.hold()
price = self.pricing_strategy.calculate_price(flight, seat)
booking = Booking(f"BK{len(self.bookings)+1}", passenger, flight, seat, price)
booking.hold_until = datetime.now() + timedelta(seconds=300)
self.bookings[booking.booking_id] = booking
return booking
Benefit: ā
Centralized creation, ā
Consistent initialization
Trade-off: ā ļø If grows, consider Builder pattern
Design Patterns Summary Table
| Pattern | Problem Solved | Benefit |
|---|---|---|
| Singleton | Need single global AirlineSystem | Consistent state across all clients |
| Strategy | Varying pricing algorithms | Pluggable, easy to extend |
| Observer | Events trigger notifications | Loose coupling, event-driven |
| State (Enum) | Explicit booking lifecycle | Invalid transitions prevented |
| Factory | Complex object creation | Centralized, consistent |
Step 06: Implementation ā Code & Concurrency
Interview Tip: Write thread-safe, defensive code. Mention "Thread Safety" even if not asked.
Complete Thread-Safe Implementation
from enum import Enum
from abc import ABC, abstractmethod
from typing import List, Dict, Optional
from datetime import datetime, timedelta
import threading
# ============ ENUMERATIONS ============
class SeatStatus(Enum):
AVAILABLE = "available"
HOLD = "hold"
BOOKED = "booked"
class BookingStatus(Enum):
HOLD = "hold"
CONFIRMED = "confirmed"
CANCELLED = "cancelled"
EXPIRED = "expired"
class SeatClass(Enum):
ECONOMY = 1
BUSINESS = 2
# ============ CORE ENTITIES ============
class Seat:
def __init__(self, seat_id: str, seat_class: SeatClass):
self.seat_id = seat_id
self.seat_class = seat_class
self.status = SeatStatus.AVAILABLE
self.booked_by = None
def is_available(self) -> bool:
return self.status == SeatStatus.AVAILABLE
def hold(self):
if not self.is_available():
raise ValueError(f"Seat {self.seat_id} not available")
self.status = SeatStatus.HOLD
def book(self):
self.status = SeatStatus.BOOKED
def release(self):
self.status = SeatStatus.AVAILABLE
self.booked_by = None
class Passenger:
def __init__(self, passenger_id: str, name: str, email: str):
self.passenger_id = passenger_id
self.name = name
self.email = email
class Flight:
def __init__(self, flight_id: str, origin: str, destination: str,
departure: datetime, aircraft_type: str):
self.flight_id = flight_id
self.origin = origin
self.destination = destination
self.departure = departure
self.aircraft_type = aircraft_type
self.seats: Dict[str, Seat] = {}
def add_seat(self, seat: Seat):
self.seats[seat.seat_id] = seat
def get_seat(self, seat_id: str) -> Optional[Seat]:
return self.seats.get(seat_id)
def available_seats_count(self) -> int:
return sum(1 for s in self.seats.values() if s.is_available())
class Booking:
def __init__(self, booking_id: str, passenger: Passenger, flight: Flight,
seat: Seat, price: float):
self.booking_id = booking_id
self.passenger = passenger
self.flight = flight
self.seat = seat
self.price = price
self.status = BookingStatus.HOLD
self.created_at = datetime.now()
self.hold_until: Optional[datetime] = None
def confirm(self):
self.status = BookingStatus.CONFIRMED
self.seat.book()
def cancel(self):
self.status = BookingStatus.CANCELLED
self.seat.release()
def expire(self):
if self.status == BookingStatus.HOLD:
self.status = BookingStatus.EXPIRED
self.seat.release()
# ============ STRATEGIES ============
class PricingStrategy(ABC):
@abstractmethod
def calculate_price(self, flight: Flight, seat: Seat) -> float:
pass
class FixedPricing(PricingStrategy):
def calculate_price(self, flight: Flight, seat: Seat) -> float:
return 200.0 if seat.seat_class == SeatClass.BUSINESS else 100.0
class DemandBasedPricing(PricingStrategy):
def calculate_price(self, flight: Flight, seat: Seat) -> float:
base = 200.0 if seat.seat_class == SeatClass.BUSINESS else 100.0
available = flight.available_seats_count()
total = len(flight.seats)
occupancy_rate = 1.0 - (available / total) if total > 0 else 0
multiplier = 1.0 + (occupancy_rate * 0.5)
return base * multiplier
# ============ OBSERVERS ============
class Observer(ABC):
@abstractmethod
def update(self, event: str, booking: Booking):
pass
class ConsoleObserver(Observer):
def update(self, event: str, booking: Booking):
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {event.upper():12} | Passenger: {booking.passenger.name:15} | "
f"Flight: {booking.flight.flight_id:8} | Seat: {booking.seat.seat_id:4} | Price: ${booking.price:.2f}")
class EmailNotifier(Observer):
def update(self, event: str, booking: Booking):
if event == "confirmed":
print(f"š§ Email: {booking.passenger.email} - Booking confirmed!")
elif event == "expired":
print(f"š§ Email: {booking.passenger.email} - Hold expired!")
# ============ SINGLETON CONTROLLER ============
class AirlineSystem:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not hasattr(self, 'initialized'):
self.flights: Dict[str, Flight] = {}
self.passengers: Dict[str, Passenger] = {}
self.bookings: Dict[str, Booking] = {}
self.observers: List[Observer] = []
self.pricing_strategy: PricingStrategy = FixedPricing()
self._data_lock = threading.Lock()
self.initialized = True
@staticmethod
def get_instance() -> 'AirlineSystem':
return AirlineSystem()
def set_pricing_strategy(self, strategy: PricingStrategy):
with self._data_lock:
self.pricing_strategy = strategy
def add_observer(self, observer: Observer):
with self._data_lock:
self.observers.append(observer)
def notify_observers(self, event: str, booking: Booking):
with self._data_lock:
observers_copy = list(self.observers)
for obs in observers_copy:
obs.update(event, booking)
def add_flight(self, flight: Flight):
with self._data_lock:
self.flights[flight.flight_id] = flight
def register_passenger(self, passenger: Passenger):
with self._data_lock:
self.passengers[passenger.passenger_id] = passenger
def hold_seat(self, passenger_id: str, flight_id: str, seat_id: str,
hold_seconds: int = 300) -> Optional[Booking]:
with self._data_lock:
if flight_id not in self.flights:
print(f"ā Flight {flight_id} not found")
return None
flight = self.flights[flight_id]
seat = flight.get_seat(seat_id)
if not seat or not seat.is_available():
print(f"ā Seat {seat_id} not available")
return None
passenger = self.passengers.get(passenger_id)
if not passenger:
print(f"ā Passenger {passenger_id} not found")
return None
seat.hold()
price = self.pricing_strategy.calculate_price(flight, seat)
booking = Booking(f"BK{len(self.bookings)+1}", passenger, flight, seat, price)
booking.hold_until = datetime.now() + timedelta(seconds=hold_seconds)
self.bookings[booking.booking_id] = booking
booking_to_notify = booking
self.notify_observers("held", booking_to_notify)
return booking
def confirm_booking(self, booking_id: str) -> bool:
with self._data_lock:
booking = self.bookings.get(booking_id)
if not booking:
print(f"ā Booking {booking_id} not found")
return False
if booking.status != BookingStatus.HOLD:
print(f"ā Booking not in HOLD status")
return False
if datetime.now() > booking.hold_until:
booking.expire()
booking_to_notify = booking
expired = True
else:
booking.confirm()
booking_to_notify = booking
expired = False
if expired:
self.notify_observers("expired", booking_to_notify)
print(f"ā Hold expired for booking {booking_id}")
return False
self.notify_observers("confirmed", booking_to_notify)
return True
def cancel_booking(self, booking_id: str) -> bool:
with self._data_lock:
booking = self.bookings.get(booking_id)
if not booking:
return False
booking.cancel()
booking_to_notify = booking
self.notify_observers("cancelled", booking_to_notify)
return True
def check_and_expire_holds(self):
now = datetime.now()
expired_bookings = []
with self._data_lock:
for booking in self.bookings.values():
if (booking.status == BookingStatus.HOLD and
booking.hold_until and now > booking.hold_until):
booking.expire()
expired_bookings.append(booking)
for booking in expired_bookings:
self.notify_observers("expired", booking)
# ============ DEMO ============
if __name__ == "__main__":
print("="*70)
print("AIRLINE MANAGEMENT SYSTEM")
print("="*70)
system = AirlineSystem.get_instance()
system.flights.clear()
system.passengers.clear()
system.bookings.clear()
system.observers.clear()
system.add_observer(ConsoleObserver())
system.add_observer(EmailNotifier())
# Create flight
flight = Flight("AA101", "NYC", "LAX", datetime.now() + timedelta(hours=2), "Boeing 737")
for i in range(1, 6):
flight.add_seat(Seat(f"{i}A", SeatClass.BUSINESS))
flight.add_seat(Seat(f"{i}B", SeatClass.ECONOMY))
system.add_flight(flight)
# Register passengers
p1 = Passenger("P001", "John Doe", "john@example.com")
p2 = Passenger("P002", "Jane Smith", "jane@example.com")
system.register_passenger(p1)
system.register_passenger(p2)
print(f"\nā
Setup: Flight {flight.flight_id} with {len(flight.seats)} seats")
# Demo hold & confirm
print("\n[Demo] Hold & Confirm:")
b1 = system.hold_seat("P001", "AA101", "1A", hold_seconds=3)
if b1:
print(f"Held booking {b1.booking_id}")
system.confirm_booking(b1.booking_id)
# Demo pricing
print("\n[Demo] Dynamic Pricing:")
system.set_pricing_strategy(DemandBasedPricing())
b2 = system.hold_seat("P002", "AA101", "1B", hold_seconds=3)
if b2:
print(f"Held booking {b2.booking_id}")
system.cancel_booking(b2.booking_id)
print(f"\nā
Demo complete! Available seats: {flight.available_seats_count()}")
Thread-Safety Analysis
| Operation | Lock Strategy | Guarantees |
|---|---|---|
| hold_seat | Mutex on data | Only 1 user can hold seat at a time |
| confirm_booking | Mutex on data | Atomic: check HOLD, check expiry, transition |
| cancel_booking | Mutex on data | No double-cancel |
| check_and_expire_holds | Mutex on data | Safe scan, expiry without race condition |
Concurrency Principles: 1. ā Locks guard critical sections (check + modify) 2. ā Minimize lock duration (notify outside lock) 3. ā Double-checked locking for Singleton 4. ā No nested locks (prevent deadlock)
Demo Scenarios
Scenario 1: Hold & Confirm
[12:30:45] HELD | Passenger: John Doe | Flight: AA101 | Seat: 1A | Price: $200.00
š§ Email: john@example.com - Booking confirmed!
[12:30:46] CONFIRMED | Passenger: John Doe | Flight: AA101 | Seat: 1A | Price: $200.00
Scenario 2: Hold Expiry
[12:31:00] HELD | Passenger: Jane Smith | Flight: AA101 | Seat: 1B | Price: $100.00
š§ Email: jane@example.com - Hold expired!
[12:31:05] EXPIRED | Passenger: Jane Smith | Flight: AA101 | Seat: 1B | Price: $100.00
Scenario 3: Dynamic Pricing
Current occupancy: 1/10 (10%)
Price: Economy $100, Business $200 (base)
After 7 bookings (80% occupancy):
Price: Economy $140 (40% surge), Business $280 (40% surge)
Interview Q&A
Basic Questions
Q1: How do you prevent overbooking of a seat?
A: Atomic status transitions with lock-based concurrency control:
with self._data_lock: # Atomic critical section
if seat.status != SeatStatus.AVAILABLE:
raise SeatNotAvailableError()
seat.status = SeatStatus.HOLD # Atomic transition
Q2: What's the difference between HOLD and CONFIRMED?
A: HOLD = temporary 5-min window. CONFIRMED = permanent after payment. Timeline: Browse ā Hold ā Confirm (or timeout ā Expire).
Q3: How do you handle hold expiry?
A: Lazy check on confirm + eager background job scanning all HOLD bookings every minute.
Q4: Why use Strategy pattern for pricing?
A: Allows swapping pricing algorithms (FixedPricing, DemandBasedPricing) without modifying booking logic.
Q5: How would you scale to 1M concurrent users and 1M flights/day?
A: Multi-tier: API Gateway ā 1000 API servers ā Redis locks ā Sharded DB (1000 shards) ā Kafka for events.
Intermediate Questions
Q6: How to handle race condition: 2 users holding same seat simultaneously?
A: Lock ensures only 1 thread can modify seat status at a time. Lock TTL prevents deadlocks.
Q7: How to handle payment failure gracefully?
A: Retry 3x with exponential backoff. On final failure, auto-expire booking and release seat.
Q8: How to implement seat upgrades (Economy ā Business)?
A: Calculate price difference, charge user, release old seat, book new seat.
Q9: What metrics would you track?
A: Hold success rate, confirmation rate, hold expiry rate, overbooking incidents, API latency, cache hit ratio.
Q10: How to handle regulatory compliance (refund policies)?
A: Time-based: Full refund if >24h, 50% if >6h, None if <6h. Audit log all refunds.
Scaling Q&A
Q1: How to prevent overbooking in distributed system?
Solution: Pessimistic locking + version control with Redis:
Acquire lock: Redis.SET("lock:FL123:1A", "USER_A", NX, EX=15s)
Check seat version: DB.GET(seat, version=5)
Update: DB.UPDATE(...) WHERE version=5 SET version=6, status=HOLD
Release lock
Q2: How to cache seat availability without invalidation storms?
Solution: Cache with 5-second TTL + versioning. Broadcast version increments to invalidate.
Q3: How to implement real-time seat updates?
Solution: WebSocket + Redis Pub/Sub. Latency: ~100ms (broadcast + network + UI update).
Success Checklist
- [ ] Explain all 6 steps: Setup ā Structure ā Interface ā Architecture ā Optimization ā Implementation
- [ ] Draw UML class diagram with all relationships
- [ ] Discuss hold/confirm lifecycle and HOLD ā CONFIRMED transitions
- [ ] Explain how to prevent overbooking with atomic locks
- [ ] Discuss hold expiry (5-min timeout, auto-release)
- [ ] Run complete implementation without errors
- [ ] Answer 5+ scaling Q&A questions
- [ ] Mention thread safety in Singleton, hold_seat, confirm_booking
- [ ] Discuss trade-offs (in-memory vs DB, pessimistic vs optimistic locking)
Ready for interview? Book some flights! š«