Low-Level Design: Hotel Booking System (OOP)
The Hotel Booking System is a rich LLD problem that combines real-world business rules with OOP design: room types, availability calendars, reservations, cancellations, and pricing strategies. It tests your ability to model time-bounded resources with complex state transitions.
Requirements
- Multiple room types: Standard, Deluxe, Suite, with different capacities and prices
- Book a room for a date range; check availability before booking
- Cancel a reservation (with optional cancellation policy)
- Handle concurrent booking requests for the same room
- Support amenities (WiFi, breakfast, parking) as add-ons
- Generate invoices with itemized charges
Core Classes
Room Hierarchy and Pricing
from enum import Enum
from datetime import date, timedelta
from typing import Optional
import threading
class RoomType(Enum):
STANDARD = "standard"
DELUXE = "deluxe"
SUITE = "suite"
class RoomStatus(Enum):
AVAILABLE = "available"
OCCUPIED = "occupied"
MAINTENANCE = "maintenance"
class Amenity(Enum):
WIFI = "wifi"
BREAKFAST = "breakfast"
PARKING = "parking"
SPA = "spa"
AMENITY_PRICES = {
Amenity.WIFI: 0.0, # Free
Amenity.BREAKFAST: 25.0, # Per night
Amenity.PARKING: 20.0, # Per night
Amenity.SPA: 50.0, # Per night
}
class Room:
BASE_RATES = {
RoomType.STANDARD: 100.0,
RoomType.DELUXE: 180.0,
RoomType.SUITE: 350.0,
}
def __init__(self, room_number: str, room_type: RoomType, capacity: int):
self.room_number = room_number
self.room_type = room_type
self.capacity = capacity
self.status = RoomStatus.AVAILABLE
self.amenities: set[Amenity] = set()
self._bookings: list['Reservation'] = [] # Non-overlapping date ranges
self._lock = threading.Lock()
def get_nightly_rate(self, pricing_strategy: 'PricingStrategy' = None) -> float:
base = self.BASE_RATES[self.room_type]
if pricing_strategy:
return pricing_strategy.calculate_rate(base)
return base
def is_available(self, check_in: date, check_out: date) -> bool:
with self._lock:
for res in self._bookings:
if res.status != ReservationStatus.CANCELLED:
# Overlap if not (checkout = res.check_out)
if not (check_out = res.check_out):
return False
return True
def add_reservation(self, reservation: 'Reservation'):
with self._lock:
self._bookings.append(reservation)
def cancel_reservation(self, reservation_id: str):
with self._lock:
for res in self._bookings:
if res.reservation_id == reservation_id:
res.status = ReservationStatus.CANCELLED
return True
return False
Reservation and Status Machine
import uuid
from dataclasses import dataclass, field
from datetime import datetime
class ReservationStatus(Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
CHECKED_IN = "checked_in"
CHECKED_OUT = "checked_out"
CANCELLED = "cancelled"
@dataclass
class Guest:
guest_id: str
name: str
email: str
phone: str
@dataclass
class Reservation:
reservation_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8].upper())
guest: Guest = None
room: Room = None
check_in: date = None
check_out: date = None
amenities: set[Amenity] = field(default_factory=set)
status: ReservationStatus = ReservationStatus.PENDING
created_at: datetime = field(default_factory=datetime.now)
@property
def nights(self) -> int:
return (self.check_out - self.check_in).days
def calculate_total(self, pricing_strategy=None) -> float:
nightly = self.room.get_nightly_rate(pricing_strategy)
amenity_cost = sum(AMENITY_PRICES[a] for a in self.amenities)
return (nightly + amenity_cost) * self.nights
def generate_invoice(self, pricing_strategy=None) -> dict:
nightly = self.room.get_nightly_rate(pricing_strategy)
amenity_breakdown = {a.value: AMENITY_PRICES[a] * self.nights
for a in self.amenities}
return {
'reservation_id': self.reservation_id,
'guest': self.guest.name,
'room': self.room.room_number,
'check_in': str(self.check_in),
'check_out': str(self.check_out),
'nights': self.nights,
'room_charges': nightly * self.nights,
'amenity_charges': amenity_breakdown,
'total': self.calculate_total(pricing_strategy),
}
def can_cancel(self) -> bool:
return self.status in (ReservationStatus.PENDING, ReservationStatus.CONFIRMED)
Pricing Strategies (Strategy Pattern)
from abc import ABC, abstractmethod
class PricingStrategy(ABC):
@abstractmethod
def calculate_rate(self, base_rate: float) -> float:
pass
class StandardPricing(PricingStrategy):
def calculate_rate(self, base_rate: float) -> float:
return base_rate
class WeekendPricing(PricingStrategy):
WEEKEND_MULTIPLIER = 1.3
def calculate_rate(self, base_rate: float) -> float:
return base_rate * self.WEEKEND_MULTIPLIER
class SeasonalPricing(PricingStrategy):
def __init__(self, season_multiplier: float):
self.multiplier = season_multiplier
def calculate_rate(self, base_rate: float) -> float:
return base_rate * self.multiplier
class DiscountPricing(PricingStrategy):
def __init__(self, discount_percent: float):
self.discount = discount_percent / 100.0
def calculate_rate(self, base_rate: float) -> float:
return base_rate * (1 - self.discount)
Hotel Controller and Search
class CancellationPolicy:
def __init__(self, free_cancel_days: int = 2, penalty_percent: float = 50.0):
self.free_cancel_days = free_cancel_days
self.penalty_percent = penalty_percent
def calculate_refund(self, reservation: Reservation) -> float:
days_until_checkin = (reservation.check_in - date.today()).days
total = reservation.calculate_total()
if days_until_checkin >= self.free_cancel_days:
return total # Full refund
return total * (1 - self.penalty_percent / 100.0)
class Hotel:
def __init__(self, name: str, cancellation_policy: CancellationPolicy = None):
self.name = name
self.rooms: dict[str, Room] = {}
self.reservations: dict[str, Reservation] = {}
self.cancellation_policy = cancellation_policy or CancellationPolicy()
self._lock = threading.Lock()
def add_room(self, room: Room):
self.rooms[room.room_number] = room
def search_available_rooms(self, check_in: date, check_out: date,
room_type: RoomType = None,
guests: int = 1) -> list[Room]:
available = []
for room in self.rooms.values():
if room.status == RoomStatus.MAINTENANCE:
continue
if room_type and room.room_type != room_type:
continue
if room.capacity Reservation:
with self._lock:
if room_number not in self.rooms:
raise ValueError(f"Room {room_number} not found")
room = self.rooms[room_number]
# Re-check availability under lock (prevent TOCTOU race)
if not room.is_available(check_in, check_out):
raise ValueError(f"Room {room_number} not available for selected dates")
reservation = Reservation(
guest=guest,
room=room,
check_in=check_in,
check_out=check_out,
amenities=amenities or set(),
status=ReservationStatus.CONFIRMED,
)
room.add_reservation(reservation)
self.reservations[reservation.reservation_id] = reservation
return reservation
def cancel_reservation(self, reservation_id: str) -> float:
"""Returns refund amount."""
if reservation_id not in self.reservations:
raise ValueError("Reservation not found")
reservation = self.reservations[reservation_id]
if not reservation.can_cancel():
raise ValueError(f"Cannot cancel reservation in status {reservation.status}")
refund = self.cancellation_policy.calculate_refund(reservation)
reservation.room.cancel_reservation(reservation_id)
reservation.status = ReservationStatus.CANCELLED
return refund
def check_in(self, reservation_id: str):
res = self.reservations.get(reservation_id)
if not res or res.status != ReservationStatus.CONFIRMED:
raise ValueError("Invalid reservation for check-in")
res.status = ReservationStatus.CHECKED_IN
res.room.status = RoomStatus.OCCUPIED
def check_out(self, reservation_id: str) -> dict:
res = self.reservations.get(reservation_id)
if not res or res.status != ReservationStatus.CHECKED_IN:
raise ValueError("Guest is not checked in")
res.status = ReservationStatus.CHECKED_OUT
res.room.status = RoomStatus.AVAILABLE
return res.generate_invoice()
Key Design Decisions
- TOCTOU prevention: Check availability and create reservation under the same lock to prevent two guests booking the same room simultaneously
- Strategy pattern for pricing: Inject pricing strategy at query time — easy to add seasonal, loyalty, or corporate pricing without modifying Room
- Reservation status machine: PENDING → CONFIRMED → CHECKED_IN → CHECKED_OUT; CANCELLED is terminal from PENDING or CONFIRMED
- Per-room locks: Fine-grained locking per room allows concurrent bookings of different rooms
- Cancellation policy as dependency: Inject policy into Hotel — different hotels can have different policies (non-refundable rates vs flexible)
Interview Extensions
- Overbooking: Hotels intentionally overbook by 5-10% accounting for no-shows; track overbooking buffer per room type
- Housekeeping scheduling: After checkout, assign room to housekeeping queue; room becomes available after cleaning
- Loyalty program: Track guest points; redeem for discounts; feed into DiscountPricing strategy
- Multi-property: HotelChain aggregate manages multiple Hotel instances; search across properties
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How do you design a Hotel Booking System in an OOP interview?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Key classes: Room (with room type, capacity, booking calendar), Reservation (guest, dates, amenities, status machine), Hotel (controller that manages rooms and reservations), and PricingStrategy (Strategy pattern for standard, weekend, and seasonal pricing). Critical: check availability and create reservation under the same lock to prevent TOCTOU race conditions where two guests book the same room simultaneously.”}},{“@type”:”Question”,”name”:”How do you handle concurrent hotel bookings in a thread-safe way?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Use fine-grained per-room locking rather than a single hotel-wide lock to allow concurrent bookings of different rooms. The booking flow under the room lock: (1) re-check availability, (2) create reservation, (3) add to room’s booking list. This prevents the TOCTOU (time-of-check-to-time-of-use) race where two threads both see a room as available and both try to book it.”}},{“@type”:”Question”,”name”:”What design patterns are used in a Hotel Booking System?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Strategy pattern for pricing (inject StandardPricing, WeekendPricing, or SeasonalPricing at query time). State machine for reservation lifecycle (PENDING → CONFIRMED → CHECKED_IN → CHECKED_OUT; CANCELLED is terminal). Dependency injection for CancellationPolicy (non-refundable rates vs flexible). Observer pattern extension: fire events when booking is confirmed to send email notifications and update inventory dashboards.”}},{“@type”:”Question”,”name”:”How do you implement a cancellation policy in a hotel booking system?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Model CancellationPolicy as a separate injectable class with calculate_refund(reservation) method. Compute days_until_checkin = check_in – today. If days_until_checkin >= free_cancel_days threshold, return full refund. Otherwise apply penalty: refund = total * (1 – penalty_percent/100). Different hotels or rate types inject different policies — non-refundable rooms have 0% refund, flexible rooms have full refund up to 24h before.”}}]}
🏢 Asked at: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering
🏢 Asked at: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering
🏢 Asked at: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering
🏢 Asked at: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems
🏢 Asked at: Atlassian Interview Guide
🏢 Asked at: Shopify Interview Guide
Asked at: Lyft Interview Guide