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.
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
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
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:
| Command | Arguments | Output |
|---|---|---|
room | roomId type | Room <roomId> (<type>) or Room <roomId> exists |
book | roomId checkin checkout guest | Booked <roomId> for <guest> or <roomId> unavailable or Unknown room <roomId> |
cancel | roomId checkin | Cancelled <roomId> or No booking |
available | checkin checkout | Available: <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.