Hotel Reservations: How to Promise a Room Only Once
Algomi Research · Created 2026-09-16 · Updated 2026-10-15 · System Design
Two families, one room, and a very awkward Diwali
Kiran's aunt runs a six-room guesthouse in Goa, with one big family room. Last Diwali she took bookings by phone and WhatsApp. Two families called within a minute of each other, both heard "yes, it's free," and both turned up on the 10th with luggage and grandparents. (Kiran, his aunt and every number here are made up for teaching.)
Kiran builds her a booking website so it never happens again. Two years later, the same code runs Konkan Stays, a platform listing 80,000 hotels. This post follows that growth, because every hard part of hotel booking shows up in it:
- A stay is several promises at once. A three-night booking needs all three nights, or it's worthless.
- Search and booking are different jobs. Search can be a little out of date. A booking confirmation can never be.
- Payment is slow and sometimes uncertain, and it must never be allowed to hold the inventory hostage.
In 30 seconds, as an architect would frame it: "The core of a booking system is one invariant: for each room type and night, held plus confirmed never exceeds sellable capacity. I'd store inventory per room type per night, claim every night of a stay in one transaction with a consistent lock order, and turn that claim into a time-limited hold. Payment runs outside that transaction as a tracked workflow, with an idempotency key and reconciliation for uncertain outcomes. Search reads a cache that's allowed to be stale, because the hold transaction makes the final decision."
This is an original interview design, not the internal architecture of any named booking company. It starts with one operator managing several hotels. Marketplace settlement and syncing with external booking channels are extensions.
1. The rules, and the guest who left on the 12th
Kiran's first version had an off-by-one bug. A guest staying "10 to 12 June" blocked the room on the 12th too, so the next guest couldn't arrive that day.
Represent dates as a half-open interval: [check_in, check_out). A stay from 10 June to 12 June uses the nights of the 10th and 11th, and the room is free for a new guest arriving on the 12th.
gantt
title Half-open stays: the check-out day is free for the next guest
dateFormat YYYY-MM-DD
axisFormat %d %b
section Guest A
Nights of 10 and 11 June :a1, 2026-06-10, 2d
section Guest B
Nights of 12 and 13 June :b1, 2026-06-12, 2dReading the diagram. Each bar covers the nights a guest sleeps there. Guest A's bar ends where Guest B's begins, with no overlap and no gap. That's what the half-open interval buys you: "check-out day" and "check-in day" can be the same date without a conflict.
Two more date details:
- Use the hotel's local calendar for room nights.
- Store precise event timestamps separately. An accommodation date is not a UTC timestamp rounded to a day. A Goa night and a London night are different calendar objects.
What the system must do. Guests search by location, dates and party size. They see room descriptions and prices, book, view their bookings and cancel under a stated policy. Authorised hotel staff maintain room inventory, rates and reservations. The first design assumes no deliberate overbooking.
| Rule | Why it matters |
|---|---|
| Confirm only inventory that was successfully held | Search results can be stale |
| Hold all nights or none | A guest cannot use a three-night stay missing the middle night |
| Preserve the accepted price and currency | Rates can change after booking |
| Do not charge twice for a retry | A network response can disappear |
| Staff changes must respect existing bookings | Reducing capacity cannot silently erase promises |
The invariant, which every later section protects, is: for each room type and night, held + confirmed <= sellable_capacity.
2. "Room type, or room 204?": the data model
A guest asks for "the sea-view room, please, number 204." Most hotels sell a room type ("deluxe sea view") and assign the physical room at check-in. If the product promises an exact room, you instead need non-overlapping date intervals for that room. Pick one model deliberately, because each needs a different invariant.
Konkan Stays sells room types:
| Record | Main fields | Responsibility |
|---|---|---|
| Hotel | hotel_id, location, timezone | Property information |
| RoomType | type_id, hotel_id, occupancy | Category guests can book |
| InventoryNight | type_id, local_date, capacity, held, confirmed | Authoritative nightly stock |
| Quote | quote_id, dates, price, currency, expires_at | Time-limited price offer |
| Hold | hold_id, quantity, dates, expires_at, state | Temporary inventory claim |
| Booking | booking_id, guest_id, hold_id, status, accepted_price | Durable reservation |
| PaymentAttempt | attempt_id, booking_id, provider_reference, status | External payment outcome |
- InventoryNight is the heart of the system. There is one row per room type per night, holding the counts the invariant is about.
- Make
(type_id, local_date)unique. - Money: use integer minor units (paise, cents) or decimals, together with a currency and explicit rounding rules.
3. The smallest design that works
For the aunt's guesthouse, one app and one database are plenty:
You've reached the free preview of this post.
Subscribe to the Blogs plan (₹99/mo) — or use any Algomi Suite, Career, Investor, or Trader plan — to keep reading.
See plansSources
- Stripe's idempotency contract — Stripe