Low-Level Design: Hotel Booking System (OOP Interview)

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

Scroll to Top