Skip to content

Hotel Management#

Problem statement (interviewer prompt)

Design a hotel management system: rooms with categories + pricing, search by date range + occupancy, reservation with deposit, check-in / check-out, billing for stay + extras (mini-bar / room service), housekeeping status, and analytics for the operator.

classDiagram
  class Hotel
  class Room
  class Reservation
  class Guest
  class Payment
  Hotel "1" *-- "many" Room
  Guest "1" -- "many" Reservation
  Reservation --> Room
  Reservation --> Payment
classDiagram
  direction TB

  class Hotel {
    +id
    +name
    +location
    +rooms: List~Room~
    +pricing: PricingPolicy
  }

  class Room {
    +id
    +number
    +type: RoomType
    +rate
    +amenities
    +status: RoomState
  }
  class RoomState {
    <<enumeration>>
    AVAILABLE
    OCCUPIED
    DIRTY
    OUT_OF_SERVICE
  }
  class RoomType {
    <<enumeration>>
    STD
    DELUXE
    SUITE
  }

  class Guest {
    +id
    +name
    +contact
    +loyalty
  }

  class Reservation {
    +id
    +guest
    +room: Room
    +checkin
    +checkout
    +status: ResState
    +totals
    +idempotencyKey
  }
  class ResState {
    <<enumeration>>
    PENDING
    CONFIRMED
    CHECKED_IN
    CHECKED_OUT
    CANCELLED
  }

  class PricingPolicy {
    <<interface>>
    +rate(room, dates) Money
  }
  class FlatNight
  class DynamicPricing

  class PaymentService
  class HousekeepingService
  class ConciergeService

  Hotel *-- Room
  Hotel --> PricingPolicy
  Guest "1" -- "many" Reservation
  Reservation --> Room
  Reservation --> ResState
  Hotel --> PaymentService
  Hotel --> HousekeepingService
  Hotel --> ConciergeService
  PricingPolicy <|.. FlatNight
  PricingPolicy <|.. DynamicPricing
  Room --> RoomState
  Room --> RoomType

Detailed class model with multiplicities#

The expanded model below adds payment, housekeeping, and folio aggregates with explicit cardinalities. Multiplicities make the inventory math obvious: a Hotel owns many Rooms; each Room is reserved zero or more times across distinct date windows; each Reservation carries exactly one Guest, one Payment outcome, and many FolioLine charges accumulated during the stay.

classDiagram
  direction TB
  class Hotel {
    +id
    +name
    +rooms: List~Room~
    +pricing: PricingPolicy
  }
  class Room {
    +id
    +number
    +floor
    +type: RoomType
    +baseRate
    +status: RoomState
  }
  class RoomType {
    <<enumeration>>
    SINGLE
    DOUBLE
    SUITE
  }
  class RoomState {
    <<enumeration>>
    AVAILABLE
    OCCUPIED
    DIRTY
    OUT_OF_SERVICE
  }
  class Reservation {
    +id
    +checkin
    +checkout
    +nights
    +state: ResState
    +total: Money
  }
  class ResState {
    <<enumeration>>
    REQUESTED
    CONFIRMED
    CHECKED_IN
    CHECKED_OUT
    CANCELLED
  }
  class Guest {
    +id
    +name
    +contact
    +loyaltyTier
  }
  class Payment {
    +id
    +amount: Money
    +method
    +status
    +authorizedAt
  }
  class FolioLine {
    +id
    +description
    +amount: Money
    +postedAt
  }
  class HousekeepingTask {
    +id
    +room: Room
    +assignedTo
    +status
  }
  class Notifier {
    <<interface>>
    +notify(event)
  }
  Hotel "1" *-- "many" Room
  Hotel "1" o-- "many" Reservation
  Reservation "many" --> "1" Guest
  Reservation "many" --> "1" Room
  Reservation "1" --> "1" Payment
  Reservation "1" *-- "many" FolioLine
  Room "1" -- "0..1" HousekeepingTask
  Hotel ..> Notifier
  Room --> RoomType
  Room --> RoomState
  Reservation --> ResState

Reservation aggregates FolioLine items (mini-bar, room service, taxes) so the bill is a sum at check-out; Payment is a separate aggregate because the authorisation and capture life cycle is different from the stay lifecycle. HousekeepingTask is loosely coupled to Room so a single room can be in service, awaiting cleaning, and assigned to a housekeeper without bloating the Room entity.

Reservation flow in Java#

The flow below shows the three-step booking critical path: checkAvailability filters rooms by date overlap, reserveRoom takes a row-level lock on the inventory row and inserts a tentative reservation, and confirmReservation flips state from REQUESTED to CONFIRMED only after payment authorisation. The lock window is tight; everything outside the database transaction runs without holding the lock.

public class ReservationService {
    private final RoomRepository rooms;
    private final ReservationRepository reservations;
    private final PaymentGateway payments;
    private final EventBus bus;

    public List<Room> checkAvailability(RoomType type,
                                        LocalDate in, LocalDate out, int guests) {
        return rooms.findAvailable(type, in, out, guests);
    }

    @Transactional(isolation = SERIALIZABLE)
    public Reservation reserveRoom(String guestId, long roomId,
                                   LocalDate in, LocalDate out,
                                   String idemKey) {
        Reservation prior = reservations.findByIdempotencyKey(idemKey);
        if (prior != null) return prior;

        Room room = rooms.lockForUpdate(roomId);
        if (reservations.overlapsWindow(roomId, in, out)) {
            throw new RoomUnavailable(roomId, in, out);
        }
        Reservation r = new Reservation(guestId, room, in, out);
        r.setState(ResState.REQUESTED);
        r.setIdempotencyKey(idemKey);
        r.setTotal(room.quote(in, out));
        return reservations.save(r);
    }

    public Reservation confirmReservation(long reservationId, PaymentRequest pr) {
        Reservation r = reservations.findById(reservationId);
        PaymentResult auth = payments.authorize(pr, r.getTotal());
        if (!auth.ok()) {
            r.setState(ResState.CANCELLED);
            reservations.save(r);
            throw new PaymentFailed(auth.code());
        }
        r.attachPayment(auth.paymentId());
        r.setState(ResState.CONFIRMED);
        reservations.save(r);
        bus.publish(new ReservationConfirmed(r.getId(), r.getGuestId()));
        return r;
    }
}

SERIALIZABLE isolation plus SELECT ... FOR UPDATE on the room row removes the double-book race at the cost of throughput; the idempotency-key check makes retries safe so a network blip cannot accidentally create two reservations.

Design patterns used#

The hotel domain is a textbook fit for four classical patterns, each isolating a dimension that varies independently.

  • Factory (RoomType subtypes): a RoomFactory.create(type) returns the right Room subclass or pre-configures the right amenity set. Hides the conditional that would otherwise leak into reservation, billing, and housekeeping code paths whenever a new category (Family, Accessible, Penthouse) is added.
  • Strategy (PricingPolicy): FlatNight, DynamicPricing, LoyaltyDiscount, and WeekendSurcharge all implement PricingPolicy.rate(room, dates). The hotel picks one at runtime; the booking flow never branches on the strategy in use. Swappable per market without recompile.
  • State (Reservation lifecycle): ResState formalises the transitions REQUESTED -> CONFIRMED -> CHECKED_IN -> CHECKED_OUT with CANCELLED reachable from the first two. Each state exposes only legal actions: you cannot check in a CANCELLED reservation or cancel one already CHECKED_OUT. The state object owns the guard logic, not scattered if statements.
  • Observer (notifications): ReservationConfirmed, CheckedIn, and CheckedOut events fan out through an EventBus to subscribers like EmailNotifier, SmsNotifier, HousekeepingScheduler, and LoyaltyAccrualService. Adding a new side effect (PDF invoice, partner CRM push) costs one new subscriber, no edits to the reservation service.

Common variants & follow-ups#

Interviewers usually push beyond the single-property baseline to test how the design scales and which decisions you would defer. The variants below show up in real loops.

  • Multi-property chain: introduce a Brand aggregate above Hotel; loyalty points and corporate rates roll up across properties; cross-property availability search hits a federated inventory index. Discuss whether bookings are owned by the property or the brand.
  • Dynamic pricing: replace FlatNight with DynamicPricing that consumes occupancy forecast, event-calendar boosts, and competitor scrape data. Cache quotes for 60 to 300 seconds so a search session sees stable prices.
  • Overbooking strategy: book to 105 to 115 percent of inventory based on historical no-show rate; on an actual overbook, walk the guest to a partner hotel at company expense and credit loyalty points. Discuss the legal and reputational tradeoff.
  • Cancellation policy variants: free cancel until 48 hours before check-in, half refund inside the window, no refund on no-show. Encoded as a CancellationPolicy strategy attached to the rate plan, not hard-coded.
  • Partial-room reservation (event venue): a ballroom is sold in time slots, not nights. Model Bookable as a generic interface; Room and Venue both implement it; reservation windows are intervals on a calendar rather than whole nights.
  • Group bookings: one GroupReservation aggregates many child reservations; rooming list is uploaded as a CSV; cancellation propagates with a configurable policy (cancel-all vs cancel-individual).

Edge cases#

These are the failure modes interviewers reliably probe; each one tests a different concurrency or domain invariant.

  • Double-booking race: two clients call reserveRoom for the same room and the same night. Pessimistic locking (SELECT ... FOR UPDATE on the room row inside a serializable transaction) blocks one until the other commits; the loser sees RoomUnavailable. Optimistic locking (a version column) is cheaper but the loser must retry; pick pessimistic for tight inventory and optimistic for low-contention cases.
  • Partial cancellation: a 3-night stay where the guest leaves after night 2. Cancel future folio lines, charge the early-departure fee per policy, recompute taxes on the shortened stay, and notify housekeeping that the room is available a day early.
  • No-show vs late check-in: at midnight on the check-in date with no arrival, the reservation is marked NO_SHOW and the first night is charged; if the policy allows late arrival, a late-arrival flag suppresses the cron job that would otherwise release the room.
  • Room downgrade when type unavailable: if the requested SUITE is unavailable at check-in (maintenance, prior guest extended stay), offer a DELUXE at the suite rate or refund the difference. Encoded as a DowngradePolicy; never silently change the room type on the reservation row.
  • Payment failure mid-reservation: authorisation succeeded but capture fails at check-out. Move the reservation to a PAYMENT_PENDING state; retry capture with exponential backoff; if all retries fail, escalate to manual collection and keep the room out of inventory until resolved so no follow-up guest is affected by an unresolved chargeback dispute.

Glossary & fundamentals#

Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.

Tag Concept What it is Page
HLD Idempotency & retries safe re-execution, backoff + jitter idempotency-retries
HLD Observability metrics, logs, traces, SLOs observability

Quick reference#

Functional#

  • Manage rooms, reservations, guests.
  • Check-in / check-out / housekeeping.
  • Dynamic pricing.
  • Loyalty programs.
  • Payments + invoicing.

Patterns#

  • State (room), Strategy (pricing), Repository, Saga.

Refs#

FAQ#

How do you search rooms by date range and occupancy?#

Index rooms by category and capacity, then query the Reservation table for overlapping date ranges; rooms with no overlap in the requested window are available.

How do you prevent double booking of the same room?#

Use a unique constraint on (room_id, date) or wrap the availability check and insert in a serializable transaction so two concurrent reservations cannot both succeed.

How is housekeeping status tracked?#

Each room carries a status enum like Clean, Dirty, OutOfOrder, Inspected. Check-out flips the room to Dirty and housekeeping updates it back to Clean after servicing.

What classes form the core of a hotel LLD?#

Hotel, Room, RoomCategory, Reservation, Guest, Payment, and HousekeepingTask cover the booking domain. Staff and Shift extend it for operational concerns.

How do you bill mini-bar and extras?#

Each charge is a folio line attached to the active Reservation; settle the folio total at check-out by summing line items plus the room rate per night.

Video walkthrough

Hotel Management System: Low-Level Design Explained with Code : via Concept && Coding