Design a Hotel Booking System
You're asked to design a hotel reservation system like Booking.com or Marriott's own platform. A user searches for a city and a set of dates, sees which room types are available at what price, and books one. Behind that, hotels manage their inventory, prices change constantly, and the same room must never be sold to two guests for overlapping nights.
Ticketing sells a specific seat for a specific moment. Here a booking occupies a range of nights, and a room that's free on Tuesday but booked Wednesday through Friday is available for some searches and not others. That interval structure is what makes the inventory model interesting.
Clarifying the requirements
- Do guests book a specific room or a room type? Almost always a type, such as "king, non-smoking," with the physical room assigned at check-in. This is a significant simplification and confirming it early is worth doing.
- How far ahead can bookings be made? A year or two is typical, and it sets how many nights of inventory you store per room type.
- Is overbooking allowed? Hotels deliberately overbook to offset no-shows. Asking about it signals domain awareness, and the answer changes the claim logic.
- What does search have to support? City, dates, guest count, price, and amenities. Search is a separate system from booking, and saying so early keeps the design clean.
- Can bookings be modified? Changing dates is effectively a cancel-and-rebook against different nights, and it's worth naming as such.
Assume: room-type inventory, a two-year booking window, controlled overbooking, and modifications treated as rebooking.
Back-of-envelope numbers
- Inventory rows: of per-night availability
- At roughly 100 bytes per row: , which is large but very partitionable
- Searches: average, peaking several times higher
- Bookings: , so the write rate is small compared to search
The search-to-booking ratio of roughly fifty to one means search and booking should be separate systems with separate stores, since one is a high-volume approximate query and the other is a low-volume exact transaction.
High-level architecture
- API gateway. Separates the high-volume search path from the low-volume booking path.
- Search service. Answers "hotels in this city with availability on these dates" from a denormalized index.
- Search index. Optimized for filtering and ranking, and allowed to be seconds behind reality.
- Booking service. The only writer to inventory, and the only component that decides availability authoritatively.
- Inventory DB. A row per room type per night, partitioned by hotel so a booking touches one partition.
- Payment provider. External, slow, and the reason a hold exists between selection and confirmation.
- Change stream. Propagates inventory changes to the search index.
Deep dive 1: modeling inventory as room-nights
The natural first instinct is a table of bookings with a check-in and check-out date, and then checking for overlapping ranges before accepting a new one. That model makes every availability question an interval-overlap query, and it makes the atomic claim awkward, because the thing you need to lock is "all nights in this range" rather than a row.
The model that works is one row per room type per night, holding a total count and a booked count:
A three-night stay is then three rows, and availability is a straightforward comparison on each. Several useful properties follow:
- Search becomes a scan of a date range for a hotel rather than an overlap query against every existing booking.
- Per-night pricing falls out naturally, since a rate column on the same row handles weekends and seasons without any extra structure.
- Partial availability is expressible. A guest wanting four nights where only three are open gets a clear answer rather than a silent failure.
- The row count is bounded and predictable, growing with hotels and nights rather than with booking volume.
The cost is that a booking now writes several rows instead of one, which is what the next section deals with.
Deep dive 2: claiming a range atomically
A three-night booking must take all three nights or none. Taking two and failing on the third leaves inventory consumed for a stay that never happened.
Within one hotel this is straightforward, because partitioning by hotel puts every row for the booking in the same partition and therefore inside one database transaction. The claim is a conditional update across the range, and the affected row count tells you whether it succeeded:
SQLUPDATE inventory SET booked = booked + 1
WHERE hotel_id = ? AND room_type = ?
AND date >= ? AND date < ?
AND booked < total;
If that updates fewer rows than the number of nights requested, at least one night was full, and the transaction rolls back. The comparison between requested nights and affected rows is the whole check, and it happens inside the same statement that would have made the change.
Two refinements are worth mentioning:
- Consistent row ordering prevents deadlocks. Two overlapping bookings that lock nights in different orders can wait on each other, and always updating in date order removes that possibility.
- Holds still apply where payment is separate from booking. A
heldcount alongsidebooked, with an expiry, gives the same hold-then-confirm shape used for ticketing, applied to every night in the range.
Deep dive 3: search that can be stale, booking that cannot
Search asks a fundamentally different question from booking. It wants "roughly which hotels in Barcelona have a room in mid-August, ranked by price and rating," across hundreds of properties, and it runs constantly. Booking asks "is this specific room type available on these three specific nights," once, and the answer has to be exact.
Running search against the inventory database would put a hundred times the load on the component that has to stay correct, so search runs against a denormalized index that carries the hotel's attributes plus a compact availability summary. That index is updated from an inventory change stream and is allowed to lag by seconds.
The consequence is that a user can be shown a hotel that just sold out, and the design has to handle that gracefully rather than pretend it won't happen:
- Re-verify at selection. When the user picks a room, the booking service checks authoritative inventory before presenting the payment step, so the failure surfaces early rather than after they've entered a card.
- Bias the index toward false positives. Showing a hotel that turns out to be full is a mild annoyance; hiding one that's actually available loses a booking outright.
- Keep availability coarse in the index. Storing "has some availability in this date range" rather than exact counts keeps the index small and reduces how often it needs updating.
Deep dive 4: overbooking on purpose
Hotels sell more rooms than they have, because a predictable share of guests never arrive. A design that treats the physical room count as a hard ceiling is modeling the technology correctly and the business incorrectly.
Implementing it is a small change: the ceiling in the claim becomes a sellable limit rather than the physical count, so a twenty-room hotel might sell twenty-two on a night with historically high no-show rates. The limit is set per hotel and per night from historical data, and it belongs in the inventory row rather than in application logic.
What makes this a good interview topic is the failure path. Occasionally everyone shows up, and the system has oversold. The handling is operational, walking the guest to a comparable hotel at the property's expense, but it has design implications worth naming: the system needs to know which bookings to prioritize when a room isn't available, typically by loyalty tier and length of stay, and it needs to record the incident so the overbooking model can be corrected.
Common pitfalls
- Storing bookings as date ranges and checking for overlaps. Every availability query becomes an interval scan, and the atomic claim has no natural unit to operate on.
- Booking nights one at a time. A partial failure consumes inventory for a stay that never happens.
- Updating rows in inconsistent order. Overlapping bookings deadlock against each other under load.
- Booking directly against the search index. The index is stale by design, so it will oversell.
- Treating physical capacity as the sellable limit. It leaves revenue on the table that the business expects to capture.
Leveling the answer
Related lessons
Sell assigned seats under extreme contention, with holds and a waiting room.
Search local businesses by proximity, with filters and aggregate ratings.