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

🏢 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