Low-Level Design: Hotel Reservation System (Availability, Pricing, Concurrency)

Low-Level Design: Hotel Reservation System

A hotel reservation system manages room inventory, bookings, availability checks, pricing, and cancellations. It is a common LLD interview question at companies like Airbnb, Booking.com, and Expedia.

Core Entities


from dataclasses import dataclass, field
from enum import Enum
from datetime import date, timedelta
from typing import Optional
import uuid

class RoomType(Enum):
    SINGLE = "single"
    DOUBLE = "double"
    SUITE = "suite"

class RoomStatus(Enum):
    AVAILABLE = "available"
    OCCUPIED = "occupied"
    MAINTENANCE = "maintenance"

class BookingStatus(Enum):
    CONFIRMED = "confirmed"
    CANCELLED = "cancelled"
    CHECKED_IN = "checked_in"
    CHECKED_OUT = "checked_out"
    NO_SHOW = "no_show"

BASE_RATES = {
    RoomType.SINGLE: 100,
    RoomType.DOUBLE: 150,
    RoomType.SUITE: 300,
}

@dataclass
class Room:
    room_number: str
    room_type: RoomType
    floor: int
    capacity: int
    amenities: list[str]
    status: RoomStatus = RoomStatus.AVAILABLE

@dataclass
class Guest:
    guest_id: str
    name: str
    email: str
    phone: str

@dataclass
class Booking:
    booking_id: str
    guest_id: str
    room_number: str
    check_in: date
    check_out: date
    status: BookingStatus = BookingStatus.CONFIRMED
    total_price: float = 0.0
    created_at: date = field(default_factory=date.today)

    @property
    def nights(self) -> int:
        return (self.check_out - self.check_in).days

Availability Check and Booking


class HotelService:
    def __init__(self):
        self._rooms: dict[str, Room] = {}
        self._guests: dict[str, Guest] = {}
        self._bookings: dict[str, Booking] = {}  # booking_id -> Booking
        # For availability: room -> list of (check_in, check_out, booking_id)
        self._room_calendar: dict[str, list[tuple]] = {}

    def add_room(self, room: Room) -> None:
        self._rooms[room.room_number] = room
        self._room_calendar[room.room_number] = []

    def find_available_rooms(self, check_in: date, check_out: date,
                              room_type: Optional[RoomType] = None) -> 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 self._is_available(room.room_number, check_in, check_out):
                available.append(room)
        return available

    def _is_available(self, room_number: str, check_in: date, check_out: date) -> bool:
        for (existing_in, existing_out, _) in self._room_calendar[room_number]:
            # Overlap: existing starts before requested ends AND existing ends after requested starts
            if existing_in  check_in:
                return False
        return True

    def book_room(self, guest_id: str, room_number: str,
                  check_in: date, check_out: date) -> Booking:
        if not self._guests.get(guest_id):
            raise ValueError(f"Guest {guest_id} not found")
        room = self._rooms.get(room_number)
        if not room:
            raise ValueError(f"Room {room_number} not found")
        if room.status == RoomStatus.MAINTENANCE:
            raise ValueError(f"Room {room_number} is under maintenance")
        if not self._is_available(room_number, check_in, check_out):
            raise ValueError(f"Room {room_number} not available for requested dates")

        booking_id = str(uuid.uuid4())
        nights = (check_out - check_in).days
        total = self._calculate_price(room.room_type, nights, check_in)

        booking = Booking(
            booking_id=booking_id,
            guest_id=guest_id,
            room_number=room_number,
            check_in=check_in,
            check_out=check_out,
            total_price=total,
        )
        self._bookings[booking_id] = booking
        self._room_calendar[room_number].append((check_in, check_out, booking_id))
        return booking

    def _calculate_price(self, room_type: RoomType, nights: int,
                          check_in: date) -> float:
        base = BASE_RATES[room_type] * nights
        # Weekend surcharge: +20% if any night is Fri/Sat
        weekend_nights = sum(
            1 for i in range(nights)
            if (check_in + timedelta(days=i)).weekday() in (4, 5)  # Fri=4, Sat=5
        )
        surcharge = weekend_nights * BASE_RATES[room_type] * 0.20
        return base + surcharge

Cancellation and Check-in


    def cancel_booking(self, booking_id: str) -> float:
        booking = self._bookings.get(booking_id)
        if not booking or booking.status != BookingStatus.CONFIRMED:
            raise ValueError(f"Booking {booking_id} not found or not cancellable")

        # Cancellation policy: full refund if > 48h before check-in
        hours_until_checkin = (booking.check_in - date.today()).days * 24
        refund = booking.total_price if hours_until_checkin >= 48 else 0.0

        booking.status = BookingStatus.CANCELLED
        # Remove from calendar
        calendar = self._room_calendar[booking.room_number]
        self._room_calendar[booking.room_number] = [
            entry for entry in calendar if entry[2] != booking_id
        ]
        return refund

    def check_in(self, booking_id: str) -> None:
        booking = self._bookings.get(booking_id)
        if not booking or booking.status != BookingStatus.CONFIRMED:
            raise ValueError("Invalid booking for check-in")
        if date.today()  float:
        booking = self._bookings.get(booking_id)
        if not booking or booking.status != BookingStatus.CHECKED_IN:
            raise ValueError("Cannot check out: booking not in CHECKED_IN state")

        # Calculate late checkout fee if past noon
        # (simplified: charge one extra night if same day after checkout)
        booking.status = BookingStatus.CHECKED_OUT
        self._rooms[booking.room_number].status = RoomStatus.AVAILABLE
        return booking.total_price  # in production, add incidentals

Pricing Strategy Extension


from abc import ABC, abstractmethod

class PricingStrategy(ABC):
    @abstractmethod
    def calculate(self, room_type: RoomType, nights: int, check_in: date) -> float:
        pass

class StandardPricing(PricingStrategy):
    def calculate(self, room_type: RoomType, nights: int, check_in: date) -> float:
        return BASE_RATES[room_type] * nights

class DynamicPricing(PricingStrategy):
    """Surge pricing based on occupancy rate."""
    def __init__(self, occupancy_rate: float):
        self.multiplier = 1.0 + max(0, (occupancy_rate - 0.7) * 2)  # 40% surge at 90% occupancy

    def calculate(self, room_type: RoomType, nights: int, check_in: date) -> float:
        return BASE_RATES[room_type] * nights * self.multiplier

class SeasonalPricing(PricingStrategy):
    PEAK_MONTHS = {6, 7, 8, 12}  # Summer and December
    def calculate(self, room_type: RoomType, nights: int, check_in: date) -> float:
        rate = BASE_RATES[room_type]
        if check_in.month in self.PEAK_MONTHS:
            rate *= 1.3  # 30% seasonal surcharge
        return rate * nights

Concurrency: Preventing Double Booking

In a production system, use optimistic locking: store a version number on each room’s booking list. When booking, read the current version, compute the new state, and update with WHERE version = read_version. If another transaction committed first, the update affects 0 rows — retry or surface conflict to user. Alternatively, use database-level SELECT FOR UPDATE on the room row to serialize concurrent bookings for the same room.

Asked at: Airbnb Interview Guide

Asked at: Stripe Interview Guide

Asked at: DoorDash Interview Guide

Asked at: Lyft Interview Guide

Scroll to Top