system-design

Design a Hotel / Room Booking System (Booking.com / Airbnb)

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:

Non-functional:

Out of scope: payments (separate), reviews/recommendations, dynamic pricing ML.


2. Estimation


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:


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

  1. Overlap correctness → transactional per-day decrement / exclusion constraint.
  2. Search volume → precomputed availability read model + cache; eventual consistency.
  3. Hot property/date → the per-day counter for a popular hotel on a peak date is a hot key; shard/queue.
  4. Global scale → partition by property/region; searches are geographically scoped.
  5. 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


Further reading