Facebook Pixel

Hotel Management

A hotel management system maps directly onto physical objects — rooms, bookings, guests — each with clear invariants, which makes it a real-world-entity problem. The central constraint is that no two bookings on the same room may overlap in time. The design must express that invariant cleanly, handle the full lifecycle of a booking (create, cancel, query), and keep the overlap check in one place.

See It in Action

The demo below enforces the core invariant: no two bookings on one room overlap in time. Click a check-in night, then a later check-out night in the same room to book the half-open range [check-in, check-out) — the check-in night is included, the check-out night is not, so the check-out night stays free. A range that touches an already-booked night is rejected as unavailable.

Hotel Management

Clarifying Requirements

Narrowing "design a hotel room reservation system" settles what a booking covers and when a room counts as free. The functional requirements for this walkthrough: the hotel holds a set of rooms, each identified by a room ID and typed as single, double, or suite; a booking covers a half-open date range [checkin, checkout) where the checkout day is free again; the room command registers a room (duplicate registration is an error); the book command reserves a room for a guest if and only if no existing booking overlaps the requested range, returning a confirmation or an "unavailable" error; the cancel command removes a booking by room ID and checkin day; and the available command counts how many rooms have no overlap with a given date range.

The overlap rule is the precise core of the problem, and it has a subtlety. Because the ranges are half-open, two bookings that merely touch — one guest checks out the morning the next checks in — do not share a night, and the room is free again on the checkout day. Getting that boundary exactly right is the crux of the design; we pin the condition down below.

Core Entities

Reading the requirements for nouns that own state gives a short list: a booking, a room, and the hotel. The booking is plainly pure data and the hotel is plainly the facade. The one decision that shapes the whole design is where the no-overlap rule lives — the Hotel facade could compare a request against every booking, or each Room could own the check against its own history. Before reading on, decide which class should hold the overlap rule.

Decision checkpoint

1)

The no-overlap invariant has to be enforced somewhere. Should Hotel.book compare the request against the room's bookings, or should each Room own the check?

The three entities fall out as follows.

Booking is a value that records the three things the hotel needs to remember about one reservation: checkin day, checkout day, and the guest name. It carries no behavior — it is pure data. No method on Booking exists because no requirement asks a booking to do anything.

Room owns the invariant that its bookings do not overlap. It keeps a list of confirmed Booking objects. The overlap check lives here because Room is the only entity that knows its own booking history; no caller should reach into the list and perform the check externally. Room exposes four methods: _overlaps (private, the core predicate), book (add a booking if possible), cancel (remove a booking by checkin day), and is_available (public predicate for the available command).

Hotel is the facade. It owns a dictionary from room ID to Room and implements the four commands. All routing — unknown-room errors, delegation to the right room — happens here. Nothing outside Hotel ever touches a Room directly.

Designing the Classes

Booking

Booking exists solely to name the three fields together. It has no methods. In Python it is a small class whose constructor stores the three values. In Java and TypeScript it is a struct-like class with public fields. It is intentionally minimal: the constraint is that a Booking object is never mutated after creation — cancellation removes the object from the list rather than setting a "cancelled" flag on it.

Room

The Core Entities checkpoint settled where the overlap check lives: on Room, not in Hotel.book. Putting it in Hotel would be a Tell-Don't-Ask violation — Hotel would reach into Room._bookings to make a decision Room should own. Because the check lives on Room, Hotel.book simply calls room.book(checkin, checkout, guest) and reads the boolean result.

That leaves the predicate itself. A booking covers the half-open range [checkin, checkout), so the checkout day is free again. Before reading on, decide what condition correctly detects whether a requested [checkin, checkout) overlaps an existing booking b — and why a single inequality is not enough.

Decision checkpoint

1)

For half-open ranges, when does a requested [checkin, checkout) overlap an existing booking [b.checkin, b.checkout)?

The overlap predicate _overlaps(checkin, checkout) iterates _bookings and returns True if any existing booking b satisfies checkin < b.checkout and b.checkin < checkout. The two conditions together are necessary and sufficient for half-open range overlap. A single condition would produce false positives (adjacent ranges touching at one point would incorrectly appear to overlap).

cancel(checkin) finds the booking whose checkin matches and removes it. Cancellation by checkin day, not by a reservation ID, keeps the interface minimal for this problem. If two bookings on the same room shared the same checkin day (which the book method prevents, since the overlap rule would reject any new booking whose checkin falls inside an existing range) there would be ambiguity, but the invariant rules it out.

Hotel

Hotel holds _rooms: dict[str, Room]. Every command begins with a room ID lookup. An unknown room returns an error string immediately. If the room exists, Hotel delegates to the room.

add_room checks for duplicates before inserting. book delegates overlap checking entirely to room.book. cancel delegates to room.cancel and reports "No booking" on failure. available counts rooms by iterating all rooms and calling room.is_available(checkin, checkout).


The public surface of Hotel is exactly four methods — one per command. Room exposes three public methods and one private predicate. Booking is pure data. No class has methods for duties it does not own.

Try It Yourself

Implement the hotel management system in the editor below. The four commands and their exact output strings are:

CommandArgumentsOutput
roomroomId typeRoom <roomId> (<type>) or Room <roomId> exists
bookroomId checkin checkout guestBooked <roomId> for <guest> or <roomId> unavailable or Unknown room <roomId>
cancelroomId checkinCancelled <roomId> or No booking
availablecheckin checkoutAvailable: <count>

Dates are integers (day numbers). A booking covers [checkin, checkout): the checkout day is free. Overlap rule: [a, b) overlaps [c, d) iff a < d and c < b. Here [a, b) is the new request and [c, d) an existing booking, i.e. checkin < b.checkout and b.checkin < checkout.

Invest in Yourself
Your new job is waiting. 83% of people that complete the program get a job offer. Unlock unlimited access to all content and features.
Go Pro