Low-Level Design: Hotel Management System — Room Booking, Check-In, and Billing

Core Entities

Hotel: hotel_id, name, address, star_rating, amenities (JSON array), check_in_time, check_out_time. RoomType: type_id, hotel_id, name (STANDARD, DELUXE, SUITE, PENTHOUSE), capacity (max guests), base_price, amenities. Room: room_id, hotel_id, type_id, number, floor, status (AVAILABLE, OCCUPIED, MAINTENANCE, HOUSEKEEPING), is_active. Booking: booking_id, hotel_id, guest_id, room_id (nullable until check-in), type_id, check_in_date, check_out_date, status (CONFIRMED, CHECKED_IN, CHECKED_OUT, CANCELLED, NO_SHOW), adults, children, total_amount, paid_amount, booking_source (DIRECT, OTA, WALK_IN), created_at. Guest: guest_id, first_name, last_name, email, phone, id_type, id_number, nationality, loyalty_tier (NONE, SILVER, GOLD, PLATINUM). Invoice: invoice_id, booking_id, line_items (JSON: [{desc, qty, rate, amount}]), subtotal, tax, total, issued_at.

Room Availability and Booking

class BookingService:
    def get_available_rooms(self, hotel_id: int, check_in: date,
                             check_out: date, type_id: int,
                             adults: int) -> list[Room]:
        # Find rooms of requested type not booked in the date range
        booked_room_ids = self.db.query("""
            SELECT DISTINCT room_id FROM bookings
            WHERE hotel_id = :h AND room_id IS NOT NULL
              AND status IN ('CONFIRMED', 'CHECKED_IN')
              AND check_in_date  :in
        """, h=hotel_id, out=check_out, in=check_in)

        return self.db.query("""
            SELECT r.* FROM rooms r
            JOIN room_types rt ON r.type_id = rt.type_id
            WHERE r.hotel_id = :h AND r.type_id = :t
              AND r.status = 'AVAILABLE' AND r.is_active = TRUE
              AND r.room_id NOT IN :booked
              AND rt.capacity >= :adults
        """, h=hotel_id, t=type_id, booked=booked_room_ids, adults=adults)

    def create_booking(self, request: BookingRequest) -> Booking:
        with self.db.transaction():
            available = self.get_available_rooms(
                request.hotel_id, request.check_in,
                request.check_out, request.type_id, request.adults
            )
            if not available:
                raise NoRoomsAvailableError()

            booking = Booking(
                hotel_id=request.hotel_id,
                guest_id=request.guest_id,
                type_id=request.type_id,
                check_in_date=request.check_in,
                check_out_date=request.check_out,
                status=BookingStatus.CONFIRMED,
                total_amount=self._calculate_total(request)
            )
            self.repo.save(booking)
            return booking

Dynamic Pricing (Revenue Management)

Hotel room prices are not static — they vary by occupancy, season, day of week, and booking lead time. Pricing factors: base_price (from RoomType), occupancy multiplier: if >80% of rooms are booked for a night: apply a 1.2x multiplier. If <30%: apply a 0.9x discount. Day-of-week: weekends are 1.15x on leisure hotels, weekdays 1.15x on business hotels. Lead time: booking 90+ days in advance: 0.9x. Booking within 3 days: 1.3x (last-minute premium). Events: if a major conference is in the city on those dates: 1.5x. Pricing tiers are stored in a PricingRule table and evaluated at booking time. The calculated price is locked in at booking time and stored on the Booking — future pricing changes don't affect confirmed bookings.

Check-In, Housekeeping, and Billing

Check-in flow: (1) Guest arrives, receptionist looks up the booking by booking_id or guest name. (2) Verify guest ID (id_type, id_number). (3) Assign a specific room: pick from available AVAILABLE rooms of the booked type (prefer floors the guest has stayed on before, prefer non-adjacent to noisy rooms if available). (4) Update: booking.room_id = assigned_room, booking.status = CHECKED_IN, room.status = OCCUPIED. Housekeeping state machine: OCCUPIED → HOUSEKEEPING (after check-out or daily service request) → AVAILABLE (after housekeeping complete). The housekeeping app allows staff to mark rooms as cleaned. Room is not reassignable until status = AVAILABLE. Check-out and billing: (1) Retrieve all charges: base room rate (nightly * nights_stayed), additional charges (minibar, room service, spa — added via the folio system during stay). (2) Apply loyalty discounts (GOLD: 5% off, PLATINUM: 10% off). (3) Generate invoice. (4) Process payment (charge to card on file). (5) Update room status to HOUSEKEEPING. Email invoice to guest. Folio charges: during the stay, any service charge (restaurant, spa) is added to the booking’s folio (PostCharge: charge_id, booking_id, description, amount, posted_at, posted_by).

Asked at: Airbnb Interview Guide

Asked at: Stripe Interview Guide

Asked at: Shopify Interview Guide

Asked at: Uber Interview Guide

Scroll to Top