Difficulty: Tier 2 Asked at: Booking.com, Airbnb, Agoda, Amazon Time budget: 45 min
Hotel booking looks like ticketing, but the reservation unit is a date
range, not a single seat — “room 204 from the 5th to the 8th.” That changes the availability and
overlap logic entirely: you’re checking whether an interval is free, and preventing two bookings whose
date ranges overlap. The heart is availability over date ranges and overlap-free reservation.
Prerequisites: Design Ticketing, Transactions, Indexing
1. Requirements
Functional:
- Search hotels/rooms by location, dates, guests; see availability & price.
- Book a room for a date range; prevent overlapping bookings of the same room.
- Cancel/modify bookings.
- Handle room types (many identical rooms of a type) vs specific rooms.
Non-functional:
- No double-booking — a room can’t be booked by two guests for overlapping dates.
- Read-heavy search (browsing availability) vs transactional booking.
- Global scale (millions of properties), high availability for search.
- Consistency for the actual booking.
Out of scope: payments (separate), reviews/recommendations, dynamic pricing ML.
2. Estimation
- Millions of properties, tens of millions of rooms, hundreds of millions of searches/day → search is the
high-volume path (read-heavy, cacheable-ish but date-parameterized).
- Bookings are far fewer (thousands/sec) but must be correct. Contention is lower than ticketing (bookings
spread across many properties/dates), except for hot properties/dates.
3. The core: availability over date ranges
🚨 The reservation unit is an interval [check_in, check_out). A room is available for a requested range
only if no existing booking overlaps it. Two ranges [a,b) and [c,d) overlap iff a < d AND c < b.
Two common models:
- Booking rows + overlap check: store each booking as
(room_id, check_in, check_out). To book, in a
transaction, check SELECT ... WHERE room_id = ? AND check_in < :out AND check_out > :in returns nothing,
then insert. A DB constraint / row lock prevents concurrent overlaps. Simple, exact.
- Per-day inventory: for room types (N identical rooms), track a count of available rooms per date;
booking a range decrements each day’s count (if all ≥ 1). Great for “20 rooms of this type” — you don’t
care which room, just that one’s free each night. 🚨 This per-day-count model is the scalable choice for
hotels (which sell room types, not specific rooms).
4. High-level design
flowchart TB
Client -->|search location+dates| Search[Search Service<br/>availability index]
Search --> AvailCache[(Availability cache/read model)]
Client -->|book room type, dates| Booking[Booking Service]
Booking -->|per-day decrement, txn| InvDB[(Inventory DB<br/>per-room-type per-day counts)]
Booking --> BookingDB[(Bookings record)]
InvDB -.updates.-> AvailCache
Search reads a denormalized availability read model (fast, possibly slightly stale). Booking hits the
strongly-consistent inventory to atomically decrement per-day counts and record the booking.
5. Deep dives
5a. Preventing overlapping/double bookings
For specific rooms: an exclusion constraint (Postgres range types) or a transactional overlap-check +
insert with row locking guarantees no two overlapping bookings. For room-type counts: atomically decrement
each day’s availability in a transaction — if any day would go below zero, roll back the whole booking.
🚨 The transaction spans all nights of the stay — all-or-nothing, so you never book 2 of a 3-night stay.
5b. Room types vs specific rooms
Hotels sell types (“Deluxe King,” 20 identical rooms), assigning a specific room at check-in. So model
availability as per-type per-day counts, not per-physical-room — far fewer, hotter counters but simpler
and matching the business. Airbnb (unique listings) is closer to the specific-room/overlap model. Clarify
which and design accordingly.
5c. Search & availability read model
Search over dates is expensive to compute live for millions of rooms. Maintain a precomputed availability
read model (per property/type/date) updated as bookings happen — search reads it quickly, tolerating
slight staleness. The booking transaction is the source of truth; the read model is eventually consistent.
🚨 Same split as ticketing: eventual consistency for search, strong for booking.
5d. Holds during checkout
Like ticketing, optionally hold availability for a few minutes while the user pays (decrement a reserved
count with a TTL), confirming or releasing after payment. Prevents losing the room mid-checkout on a hot
date.
5e. Cancellations & modifications
Cancelling increments the per-day counts back (and updates the read model); modifying dates = release
old range + reserve new range atomically. Handle idempotency so a retried cancel doesn’t over-credit
inventory.
6. Bottlenecks & scaling further
- Overlap correctness → transactional per-day decrement / exclusion constraint.
- Search volume → precomputed availability read model + cache; eventual consistency.
- Hot property/date → the per-day counter for a popular hotel on a peak date is a hot key; shard/queue.
- Global scale → partition by property/region; searches are geographically scoped.
- Multi-night atomicity → single transaction across the stay’s dates.
7. Trade-off summary
| Decision |
Chosen |
Alternative |
Why |
| Availability model |
Per-type per-day counts |
Per-physical-room rows |
Matches how hotels sell (types); fewer records |
| Overlap prevention |
Transaction across all nights |
Per-night independent |
All-or-nothing stay; no partial bookings |
| Search |
Precomputed read model (eventual) |
Live compute on inventory |
Read-heavy; slight staleness OK |
| Booking |
Strongly consistent inventory |
Eventual |
Must not double-book |
| Hold |
Reserve count + TTL |
Book on payment only |
Don’t lose the room while paying |
8. Follow-up questions
How is this different from booking a concert seat?
The reservation unit is a date range rather than a single point, which changes the availability logic from
"is this seat taken?" to "is this room free for every night of the requested interval, with no overlapping
booking?" Two bookings conflict when their date ranges overlap (`check_in < other_check_out` and
`other_check_in < check_out`), so preventing double-booking means preventing interval overlap, and booking a
multi-night stay must atomically secure every night — you can't book 2 of 3 nights. Hotels also typically
sell room *types* (many identical rooms) rather than specific units, so availability is naturally modeled as
a per-type, per-day count you decrement across the stay, whereas a concert seat is a unique resource with a
single AVAILABLE/BOOKED state. So while both share the reserve-then-confirm, strong-consistency-for-booking
pattern, hotels add interval-overlap logic and per-day inventory counts that ticketing's single-seat model
doesn't have.
How do you guarantee a room isn't double-booked for overlapping dates?
Within a single transaction, you check-and-reserve atomically. For specific rooms, you either use a database
exclusion constraint that forbids two bookings of the same room with overlapping ranges, or you run an
overlap query (`WHERE room_id = ? AND check_in < :out AND check_out > :in`) under a row lock and insert only
if it returns nothing — the lock serializes concurrent attempts so exactly one wins. For room-type counts,
you atomically decrement the available count for *each* day of the stay inside one transaction, and if any
single night's count would drop below zero you roll the whole thing back — this both prevents overselling a
given night and guarantees the stay is all-or-nothing. The essential points are that the availability check
and the reservation happen in the same atomic transaction (no read-then-write gap), and that a multi-night
booking is one transaction spanning every night so you never end up with a partially-booked stay.
Searching availability across millions of rooms for given dates is expensive. How do you make it fast?
You don't compute it live against the transactional inventory on every search; you maintain a precomputed
availability read model — a denormalized structure keyed by property/room-type/date that says what's
available — and serve searches from it (and from caches on top). The booking transactions remain the source
of truth, and they asynchronously update this read model as reservations and cancellations happen, so the
read model is eventually consistent and may lag reality by a little. That's acceptable for browsing: a user
might occasionally try to book something that just sold out, and the strongly-consistent booking transaction
catches that at commit time and tells them. This is the same split as ticketing and e-commerce — eventual
consistency for the high-volume read/search path, strong consistency for the actual reservation — which lets
search scale to hundreds of millions of queries without hammering the consistent inventory store.
Why model availability as per-day counts rather than per-physical-room bookings?
Because hotels sell room *types*, not specific physical rooms — a guest books "a Deluxe King," and the hotel
assigns an actual room at check-in. So what matters for booking is whether at least one room of that type is
free each night, which a per-type, per-day count captures directly: to book, decrement each night's count if
it's above zero. This is simpler and more efficient than tracking overlap intervals for every one of the
physical rooms, and it matches the business reality (guests are interchangeable across identical rooms). It
also concentrates contention onto a small set of counters (one per type per day) that are easy to reason
about transactionally. The per-physical-room, interval-overlap model is the right choice when units are
genuinely unique — like Airbnb listings or a specific suite — where the guest is booking one particular
space; there you check range overlap per listing. Clarifying which case you're in drives the model.
9. What junior / mid / senior answers look like
- Junior: stores bookings and checks availability with a naive query, missing transactional overlap
handling and multi-night atomicity — races double-book.
- Mid: per-day availability counts (or overlap constraints), transactional booking across all nights,
cached search, distinguishes room types from specific rooms.
- Senior: all that plus a precomputed eventually-consistent availability read model for search vs strong
consistency for booking, holds with TTLs for checkout, hot-property/date counters handled as hot keys,
idempotent cancel/modify that correctly re-credits inventory, and clear reasoning about the type-vs-unique
reservation models.
Further reading