Skip to main content
Difficulty: 🟡 Intermediate | Time: 45-60 minutes | Patterns: Factory, Strategy, Observer

🎯 Problem Statement

Design a hotel booking system that can:
  • Manage multiple hotels with different room types
  • Handle room reservations and cancellations
  • Process check-in and check-out
  • Calculate pricing with dynamic rates and discounts
  • Handle concurrent booking requests
Why This Problem? Hotel booking tests your ability to handle concurrent access (double booking prevention), complex pricing logic, and state transitions (reservation lifecycle).

📋 Step 1: Clarify Requirements

Interview Tip: Hotel systems can be very complex. Always clarify scope - is this one hotel or a booking platform like Booking.com?

Questions to Ask the Interviewer

CategoryQuestionImpact on Design
ScaleSingle hotel or chain/platform?Multi-tenancy design
RoomsDifferent room types? Amenities?Room class hierarchy
PricingDynamic pricing? Seasonal rates?Strategy pattern for pricing
BookingInstant booking or request-based?Confirmation flow
PaymentFull upfront or pay at hotel?Payment integration
ConcurrencyHandle simultaneous bookings?Locking strategy

Functional Requirements

  • Search available rooms by date, location, room type
  • Book rooms with guest information
  • Cancel/modify reservations
  • Check-in and check-out guests
  • Calculate total bill including extras
  • Send booking confirmations

Non-Functional Requirements

  • Handle concurrent bookings (no double-booking!)
  • Support multiple payment methods
  • Maintain booking history
  • Real-time availability updates

🧩 Step 2: Identify Core Objects

Key Insight: The booking flow uses pessimistic locking to prevent double bookings - when a user starts booking, the room is temporarily locked until payment completes or times out.

Hotel Structure

Hotel, Room, RoomType, Amenity

Booking

Reservation, Guest, Bill

Services

BookingService, PaymentService

Entity-Responsibility Mapping

EntityResponsibilitiesPattern
HotelManage rooms, handle searches-
RoomTrack availability, calculate pricing-
ReservationManage booking lifecycleState Pattern
PricingStrategyCalculate room ratesStrategy Pattern
NotificationServiceSend confirmationsObserver Pattern
PaymentProcessorHandle paymentsStrategy Pattern

Reservation State Machine

┌─────────┐  book()   ┌─────────────┐  pay()   ┌───────────┐
│ PENDING │──────────►│  CONFIRMED  │─────────►│ CHECKED_IN│
└─────────┘           └─────────────┘          └───────────┘
     │                       │                       │
     │cancel()         cancel()               checkout()
     │                       │                       │
     ▼                       ▼                       ▼
┌─────────────┐       ┌─────────────┐        ┌─────────────┐
│  CANCELLED  │       │  CANCELLED  │        │ CHECKED_OUT │
└─────────────┘       │ (refund)    │        └─────────────┘
                      └─────────────┘
                      
             timeout (no show)
  CONFIRMED ─────────────────────► NO_SHOW

📐 Step 3: Class Diagram

┌─────────────────────────────────────────────────────────────┐
│                          Hotel                              │
├─────────────────────────────────────────────────────────────┤
│ - id: UUID                                                  │
│ - name: String                                              │
│ - address: Address                                          │
│ - rooms: List<Room>                                         │
│ - roomTypes: List<RoomType>                                 │
├─────────────────────────────────────────────────────────────┤
│ + searchAvailableRooms(dates, type): List<Room>            │
│ + getRoom(roomNumber): Room                                 │
│ + addRoom(room): void                                       │
└─────────────────────────────────────────────────────────────┘

                              │ 1..*

┌─────────────────────────────────────────────────────────────┐
│                           Room                              │
├─────────────────────────────────────────────────────────────┤
│ - id: UUID                                                  │
│ - roomNumber: String                                        │
│ - type: RoomType                                            │
│ - status: RoomStatus                                        │
│ - floor: int                                                │
├─────────────────────────────────────────────────────────────┤
│ + isAvailable(checkIn, checkOut): bool                     │
│ + book(): void                                              │
│ + checkIn(): void                                           │
│ + checkOut(): void                                          │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                       Reservation                           │
├─────────────────────────────────────────────────────────────┤
│ - id: UUID                                                  │
│ - guest: Guest                                              │
│ - room: Room                                                │
│ - checkInDate: Date                                         │
│ - checkOutDate: Date                                        │
│ - status: ReservationStatus                                 │
│ - totalAmount: Decimal                                      │
├─────────────────────────────────────────────────────────────┤
│ + confirm(): void                                           │
│ + cancel(): void                                            │
│ + modify(newDates): bool                                    │
│ + addCharge(charge): void                                   │
└─────────────────────────────────────────────────────────────┘

Step 4: Implementation

Enums and Constants

from enum import Enum
from datetime import datetime, date, timedelta
from typing import Optional, List, Dict
from dataclasses import dataclass, field
from decimal import Decimal
from abc import ABC, abstractmethod
import uuid
import threading

class RoomType(Enum):
    SINGLE = "single"
    DOUBLE = "double"
    DELUXE = "deluxe"
    SUITE = "suite"
    PRESIDENTIAL = "presidential"

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

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

class PaymentStatus(Enum):
    PENDING = "pending"
    COMPLETED = "completed"
    FAILED = "failed"
    REFUNDED = "refunded"

# Base prices per night
BASE_PRICES = {
    RoomType.SINGLE: Decimal("100.00"),
    RoomType.DOUBLE: Decimal("150.00"),
    RoomType.DELUXE: Decimal("250.00"),
    RoomType.SUITE: Decimal("400.00"),
    RoomType.PRESIDENTIAL: Decimal("1000.00"),
}

Guest Class

@dataclass
class Address:
    street: str
    city: str
    state: str
    country: str
    zip_code: str

@dataclass
class Guest:
    id: str = field(default_factory=lambda: str(uuid.uuid4())[:8].upper())
    name: str = ""
    email: str = ""
    phone: str = ""
    address: Optional[Address] = None
    id_type: str = ""  # passport, driver's license, etc.
    id_number: str = ""
    loyalty_points: int = 0
    
    def add_loyalty_points(self, points: int):
        self.loyalty_points += points
    
    def redeem_points(self, points: int) -> bool:
        if points > self.loyalty_points:
            return False
        self.loyalty_points -= points
        return True

Room Classes

@dataclass
class Amenity:
    name: str
    description: str
    charge: Decimal = Decimal("0.00")

@dataclass
class RoomTypeConfig:
    type: RoomType
    base_price: Decimal
    max_occupancy: int
    amenities: List[Amenity] = field(default_factory=list)
    description: str = ""

class Room:
    def __init__(
        self, 
        room_number: str, 
        room_type: RoomType,
        floor: int
    ):
        self.id = str(uuid.uuid4())
        self.room_number = room_number
        self.room_type = room_type
        self.floor = floor
        self.status = RoomStatus.AVAILABLE
        self._reservations: List['Reservation'] = []
        self._lock = threading.Lock()
    
    def is_available(self, check_in: date, check_out: date) -> bool:
        """Check if room is available for given date range"""
        with self._lock:
            if self.status == RoomStatus.MAINTENANCE:
                return False
            
            for reservation in self._reservations:
                if reservation.status in [
                    ReservationStatus.CONFIRMED, 
                    ReservationStatus.CHECKED_IN
                ]:
                    # Check for date overlap
                    if not (check_out <= reservation.check_in_date or 
                            check_in >= reservation.check_out_date):
                        return False
            return True
    
    def add_reservation(self, reservation: 'Reservation') -> bool:
        """Add a reservation if room is available"""
        with self._lock:
            if self.is_available(reservation.check_in_date, reservation.check_out_date):
                self._reservations.append(reservation)
                return True
            return False
    
    def get_reservations(self, from_date: date = None, to_date: date = None) -> List['Reservation']:
        """Get reservations within date range"""
        reservations = []
        for res in self._reservations:
            if from_date and res.check_out_date < from_date:
                continue
            if to_date and res.check_in_date > to_date:
                continue
            reservations.append(res)
        return reservations
    
    def __str__(self):
        return f"Room {self.room_number} ({self.room_type.value})"

Pricing Strategy

class PricingStrategy(ABC):
    """Strategy pattern for dynamic pricing"""
    
    @abstractmethod
    def calculate_price(
        self, 
        base_price: Decimal, 
        check_in: date, 
        check_out: date
    ) -> Decimal:
        pass

class StandardPricing(PricingStrategy):
    """Standard pricing - base price per night"""
    
    def calculate_price(self, base_price: Decimal, check_in: date, check_out: date) -> Decimal:
        nights = (check_out - check_in).days
        return base_price * nights

class SeasonalPricing(PricingStrategy):
    """Higher prices during peak seasons"""
    
    PEAK_MONTHS = [6, 7, 8, 12]  # Summer and December
    PEAK_MULTIPLIER = Decimal("1.5")
    
    def calculate_price(self, base_price: Decimal, check_in: date, check_out: date) -> Decimal:
        total = Decimal("0.00")
        current = check_in
        
        while current < check_out:
            if current.month in self.PEAK_MONTHS:
                total += base_price * self.PEAK_MULTIPLIER
            else:
                total += base_price
            current += timedelta(days=1)
        
        return total

class WeekendPricing(PricingStrategy):
    """Higher prices on weekends"""
    
    WEEKEND_MULTIPLIER = Decimal("1.25")
    
    def calculate_price(self, base_price: Decimal, check_in: date, check_out: date) -> Decimal:
        total = Decimal("0.00")
        current = check_in
        
        while current < check_out:
            if current.weekday() >= 5:  # Saturday = 5, Sunday = 6
                total += base_price * self.WEEKEND_MULTIPLIER
            else:
                total += base_price
            current += timedelta(days=1)
        
        return total

class DynamicPricing(PricingStrategy):
    """Combines seasonal and weekend pricing"""
    
    def __init__(self, occupancy_rate: float = 0.5):
        self.occupancy_rate = occupancy_rate
    
    def calculate_price(self, base_price: Decimal, check_in: date, check_out: date) -> Decimal:
        total = Decimal("0.00")
        current = check_in
        
        while current < check_out:
            price = base_price
            
            # Peak season multiplier
            if current.month in [6, 7, 8, 12]:
                price *= Decimal("1.3")
            
            # Weekend multiplier
            if current.weekday() >= 5:
                price *= Decimal("1.2")
            
            # High occupancy multiplier
            if self.occupancy_rate > 0.8:
                price *= Decimal("1.15")
            
            total += price
            current += timedelta(days=1)
        
        return total.quantize(Decimal("0.01"))

Bill and Charges

@dataclass
class Charge:
    description: str
    amount: Decimal
    timestamp: datetime = field(default_factory=datetime.now)
    category: str = "room"  # room, food, service, tax

class Bill:
    def __init__(self, reservation: 'Reservation'):
        self.id = str(uuid.uuid4())[:8].upper()
        self.reservation = reservation
        self.charges: List[Charge] = []
        self.payments: List[Dict] = []
        self.created_at = datetime.now()
    
    def add_charge(self, charge: Charge):
        self.charges.append(charge)
    
    def add_room_charge(self, amount: Decimal):
        self.add_charge(Charge("Room Charge", amount, category="room"))
    
    def add_service_charge(self, description: str, amount: Decimal):
        self.add_charge(Charge(description, amount, category="service"))
    
    def get_subtotal(self) -> Decimal:
        return sum(c.amount for c in self.charges)
    
    def get_tax(self, rate: Decimal = Decimal("0.12")) -> Decimal:
        return (self.get_subtotal() * rate).quantize(Decimal("0.01"))
    
    def get_total(self) -> Decimal:
        return self.get_subtotal() + self.get_tax()
    
    def get_balance_due(self) -> Decimal:
        paid = sum(Decimal(str(p["amount"])) for p in self.payments)
        return self.get_total() - paid
    
    def add_payment(self, amount: Decimal, method: str):
        self.payments.append({
            "amount": float(amount),
            "method": method,
            "timestamp": datetime.now()
        })
    
    def print_bill(self):
        print(f"\n{'='*50}")
        print(f"BILL #{self.id}")
        print(f"Reservation: {self.reservation.id}")
        print(f"Guest: {self.reservation.guest.name}")
        print(f"{'='*50}")
        
        for charge in self.charges:
            print(f"{charge.description:30} ${charge.amount:>10.2f}")
        
        print(f"{'-'*50}")
        print(f"{'Subtotal':30} ${self.get_subtotal():>10.2f}")
        print(f"{'Tax (12%)':30} ${self.get_tax():>10.2f}")
        print(f"{'='*50}")
        print(f"{'TOTAL':30} ${self.get_total():>10.2f}")
        
        if self.payments:
            print(f"\nPayments:")
            for p in self.payments:
                print(f"  {p['method']:25} ${p['amount']:>10.2f}")
        
        print(f"{'Balance Due':30} ${self.get_balance_due():>10.2f}")
        print(f"{'='*50}\n")

Reservation Class

class Reservation:
    def __init__(
        self,
        guest: Guest,
        room: Room,
        check_in_date: date,
        check_out_date: date,
        pricing_strategy: PricingStrategy = None
    ):
        self.id = str(uuid.uuid4())[:8].upper()
        self.guest = guest
        self.room = room
        self.check_in_date = check_in_date
        self.check_out_date = check_out_date
        self.status = ReservationStatus.PENDING
        self.created_at = datetime.now()
        self.pricing_strategy = pricing_strategy or StandardPricing()
        self.bill: Optional[Bill] = None
        self.special_requests: List[str] = []
        
        # Calculate initial room charge
        self._room_charge = self._calculate_room_charge()
    
    def _calculate_room_charge(self) -> Decimal:
        base_price = BASE_PRICES[self.room.room_type]
        return self.pricing_strategy.calculate_price(
            base_price, self.check_in_date, self.check_out_date
        )
    
    @property
    def nights(self) -> int:
        return (self.check_out_date - self.check_in_date).days
    
    def confirm(self) -> bool:
        if self.status != ReservationStatus.PENDING:
            return False
        
        if self.room.add_reservation(self):
            self.status = ReservationStatus.CONFIRMED
            self.bill = Bill(self)
            self.bill.add_room_charge(self._room_charge)
            print(f"Reservation {self.id} confirmed!")
            return True
        
        print("Room not available for selected dates!")
        return False
    
    def check_in(self) -> bool:
        if self.status != ReservationStatus.CONFIRMED:
            print("Reservation must be confirmed before check-in!")
            return False
        
        if date.today() < self.check_in_date:
            print("Check-in date not reached yet!")
            return False
        
        self.status = ReservationStatus.CHECKED_IN
        self.room.status = RoomStatus.OCCUPIED
        print(f"Guest {self.guest.name} checked into {self.room}")
        return True
    
    def check_out(self) -> Optional[Bill]:
        if self.status != ReservationStatus.CHECKED_IN:
            print("Guest not checked in!")
            return None
        
        self.status = ReservationStatus.CHECKED_OUT
        self.room.status = RoomStatus.CLEANING
        
        # Add loyalty points
        points = int(self._room_charge / 10)
        self.guest.add_loyalty_points(points)
        print(f"Added {points} loyalty points!")
        
        return self.bill
    
    def cancel(self, refund_policy: str = "flexible") -> Decimal:
        """Cancel reservation and calculate refund"""
        if self.status in [ReservationStatus.CHECKED_IN, ReservationStatus.CHECKED_OUT]:
            print("Cannot cancel - guest already checked in/out!")
            return Decimal("0.00")
        
        self.status = ReservationStatus.CANCELLED
        
        # Calculate refund based on policy
        if refund_policy == "flexible":
            days_until_checkin = (self.check_in_date - date.today()).days
            if days_until_checkin >= 7:
                refund = self._room_charge
            elif days_until_checkin >= 3:
                refund = self._room_charge * Decimal("0.5")
            else:
                refund = Decimal("0.00")
        elif refund_policy == "non-refundable":
            refund = Decimal("0.00")
        else:  # moderate
            days_until_checkin = (self.check_in_date - date.today()).days
            if days_until_checkin >= 5:
                refund = self._room_charge
            else:
                refund = Decimal("0.00")
        
        print(f"Reservation cancelled. Refund: ${refund}")
        return refund
    
    def add_charge(self, description: str, amount: Decimal):
        """Add extra charge (room service, minibar, etc.)"""
        if self.bill:
            self.bill.add_service_charge(description, amount)
    
    def __str__(self):
        return (f"Reservation {self.id}: {self.guest.name} - "
                f"{self.room} from {self.check_in_date} to {self.check_out_date}")

Hotel Class

class Hotel:
    def __init__(self, name: str, address: Address):
        self.id = str(uuid.uuid4())
        self.name = name
        self.address = address
        self.rooms: List[Room] = []
        self.reservations: List[Reservation] = []
        self._lock = threading.Lock()
    
    def add_room(self, room: Room):
        self.rooms.append(room)
    
    def search_available_rooms(
        self, 
        check_in: date, 
        check_out: date,
        room_type: RoomType = None
    ) -> List[Room]:
        """Search for available rooms"""
        available = []
        
        for room in self.rooms:
            if room_type and room.room_type != room_type:
                continue
            if room.is_available(check_in, check_out):
                available.append(room)
        
        return available
    
    def make_reservation(
        self,
        guest: Guest,
        room: Room,
        check_in: date,
        check_out: date,
        pricing_strategy: PricingStrategy = None
    ) -> Optional[Reservation]:
        """Create a new reservation"""
        with self._lock:
            if not room.is_available(check_in, check_out):
                print(f"Room {room.room_number} not available!")
                return None
            
            reservation = Reservation(
                guest=guest,
                room=room,
                check_in_date=check_in,
                check_out_date=check_out,
                pricing_strategy=pricing_strategy
            )
            
            if reservation.confirm():
                self.reservations.append(reservation)
                return reservation
            
            return None
    
    def find_reservation(self, reservation_id: str) -> Optional[Reservation]:
        for res in self.reservations:
            if res.id == reservation_id:
                return res
        return None
    
    def get_occupancy_rate(self, target_date: date = None) -> float:
        """Calculate occupancy rate for a given date"""
        target_date = target_date or date.today()
        occupied = 0
        
        for room in self.rooms:
            if room.status == RoomStatus.MAINTENANCE:
                continue
            for res in room._reservations:
                if (res.check_in_date <= target_date < res.check_out_date and
                    res.status in [ReservationStatus.CONFIRMED, ReservationStatus.CHECKED_IN]):
                    occupied += 1
                    break
        
        available_rooms = len([r for r in self.rooms if r.status != RoomStatus.MAINTENANCE])
        return occupied / available_rooms if available_rooms > 0 else 0.0
    
    def get_revenue_report(self, from_date: date, to_date: date) -> Dict:
        """Generate revenue report for date range"""
        total_revenue = Decimal("0.00")
        room_nights = 0
        
        for res in self.reservations:
            if res.status in [ReservationStatus.CHECKED_OUT, ReservationStatus.CHECKED_IN]:
                if res.check_in_date >= from_date and res.check_out_date <= to_date:
                    if res.bill:
                        total_revenue += res.bill.get_total()
                    room_nights += res.nights
        
        return {
            "from_date": from_date,
            "to_date": to_date,
            "total_revenue": total_revenue,
            "room_nights_sold": room_nights,
            "average_daily_rate": total_revenue / room_nights if room_nights > 0 else Decimal("0.00")
        }

Step 5: Usage Example

# Create hotel
address = Address("123 Main St", "New York", "NY", "USA", "10001")
hotel = Hotel("Grand Plaza Hotel", address)

# Add rooms
for floor in range(1, 6):
    for room_num in range(1, 11):
        room_number = f"{floor}{room_num:02d}"
        if room_num <= 4:
            room_type = RoomType.SINGLE
        elif room_num <= 7:
            room_type = RoomType.DOUBLE
        elif room_num <= 9:
            room_type = RoomType.DELUXE
        else:
            room_type = RoomType.SUITE
        
        hotel.add_room(Room(room_number, room_type, floor))

print(f"Hotel has {len(hotel.rooms)} rooms")

# Create a guest
guest = Guest(
    name="John Smith",
    email="[email protected]",
    phone="555-1234"
)

# Search for available rooms
check_in = date(2024, 7, 15)
check_out = date(2024, 7, 18)

available = hotel.search_available_rooms(check_in, check_out, RoomType.DELUXE)
print(f"Found {len(available)} available deluxe rooms")

# Make a reservation with seasonal pricing
if available:
    room = available[0]
    reservation = hotel.make_reservation(
        guest=guest,
        room=room,
        check_in=check_in,
        check_out=check_out,
        pricing_strategy=SeasonalPricing()
    )
    
    if reservation:
        print(reservation)
        print(f"Total: ${reservation.bill.get_total()}")
        
        # Check in
        reservation.check_in()
        
        # Add some charges
        reservation.add_charge("Room Service - Dinner", Decimal("45.00"))
        reservation.add_charge("Mini Bar", Decimal("22.00"))
        
        # Check out
        bill = reservation.check_out()
        bill.print_bill()
        
        # Make payment
        bill.add_payment(bill.get_total(), "Credit Card")
        print(f"Balance Due: ${bill.get_balance_due()}")

Key Design Decisions

Hotels have complex pricing: seasonal rates, weekend premiums, demand-based pricing, promotions. Strategy pattern allows swapping pricing algorithms without changing reservation logic.
Two users might try to book the same room simultaneously. The lock ensures atomic check-and-book operations, preventing double bookings.
Bills can have multiple charges added over time (room service, minibar). Separating them follows SRP and makes billing logic reusable.
Reservations follow a clear lifecycle: Pending → Confirmed → Checked In → Checked Out. Status prevents invalid transitions and documents the flow.

Extension Points

Interview Extensions - Be ready to discuss:
  • Room Assignment: Auto-assign best available room based on preferences
  • Overbooking: Industry practice to overbook by 5-10%
  • Group Bookings: Multiple rooms, one reservation
  • Loyalty Tiers: Different perks for gold/platinum members
  • Channel Management: Different rates for different booking sources