Design hotel booking (Airbnb / Booking.com)
The interviewer asks
“Design a hotel and home-stay booking platform like Booking.com or Airbnb. Guests search by city or map with dates and guests, see available properties with prices, and book. Hotels also sell the same rooms on other sites. How do you show accurate availability and never double-book a room?”
What a strong answer looks like
A strong answer separates the read-heavy search path from the write-critical booking path. Inventory is modelled per property, per room type, per night: a row with total and booked counts (hotels) or a single unit per night (Airbnb-style homes). Booking a 3-night stay must atomically decrement all 3 nights or none, done with a single SQL transaction and conditional updates on those rows, plus an idempotency key. Search runs on Elasticsearch with geo and filters, but availability and price for specific dates change constantly, so the strong answer keeps a denormalised availability/price index updated by CDC and re-checks the top results against the source of truth before display. Hotels sell the same rooms via channel managers on many sites, so inventory sync is an integration problem with real double-booking risk; explain how you handle it (allotments, fast push updates, overbooking policy). Deep dives: the inventory model and atomic multi-night booking, search with dates, and channel manager sync.
Concepts to mention
1 Requirements
Functional
- Search by city, landmark or map area with check-in/check-out dates and guest count; filter by price, rating, amenities.
- Show only properties with availability for all nights, with total price including taxes.
- Property page with room types, photos, reviews, cancellation policy.
- Book a room (or a whole home) with payment now or pay at hotel.
- Hosts and hotels manage inventory, rates and restrictions (minimum stay, closed dates), directly or via a channel manager.
- Cancel and modify bookings with refunds per policy.
- Guest and host messaging and reviews after the stay.
Non-functional
- Search p95 under 800 ms; property page under 500 ms.
- Zero double-bookings caused by our system; booking is strongly consistent.
- Availability shown in search may be up to ~1 minute stale; re-checked before booking.
- 99.99% availability for booking; 99.9% for search.
- Handle long-weekend and holiday peaks of 5x search traffic.
- Guest payment and identity data protected (PCI scope minimal, PII encrypted).
Out of scope (say it out loud)
- Dynamic pricing model for hosts (mention only).
- Review moderation and trust & safety details.
- Host payouts accounting beyond a mention.
2 Estimates
3 API
| Endpoint | What it does |
|---|---|
GET/v1/search?bbox=|city=&checkin=&checkout=&guests=&filters= | List available properties with lowest total price for the stay; cursor-paginated. |
GET/v1/properties/{id}/availability?checkin=&checkout=&guests= | Live availability and price per room type from the source of truth. |
POST/v1/bookings | Book {property_id, room_type_id, checkin, checkout, guests, rate_plan_id}; Idempotency-Key; returns booking + payment intent. |
POST/v1/bookings/{id}/cancel | Cancel; releases inventory and refunds per policy. |
PUT/v1/partner/properties/{id}/inventory | Hotel or channel manager updates availability, rates and restrictions for a date range. |
POST/v1/partner/webhooks/bookings | We notify the hotel/channel manager of new bookings and cancellations. |
4 Data model
properties / room_typesPostgresproperty_idhost_idnamelocation (lat,lng)amenitiespolicies; room_type_idproperty_idmax_gueststotal_unitsSource of truth for descriptive data; streamed to the search index via CDC.
inventoryPostgres sharded by property_id(room_type_id, night) PKtotalbookedheldclosedmin_stayversionOne row per room type per night. Available = total - booked - held. All nights of a property live on one shard so a booking is a single-shard transaction.
ratesPostgres sharded by property_id(rate_plan_id, night) PKpricecurrencyrefundablebreakfastPrice can differ per night; total = sum over nights + taxes.
bookingsPostgres sharded by property_id (copy by guest_id for 'my trips')booking_idguest_idproperty_idroom_type_idcheckincheckouttotalstatusidempotency_key (unique)channelA guest-sharded read model is maintained asynchronously for the guest's trip list.
search_indexElasticsearchproperty doc: geo_pointcityamenitiesratingmin price; nested or side-index of availability per night for next ~180 daysUpdated by CDC from inventory and rates; approximate, re-checked before display of top results.
holdsPostgres + TTL sweeperhold_idroom_type_idnightsexpires_atShort holds (~10 min) during payment, so the room is not sold twice while the guest pays.
5 Architecture
Hover a box to highlight its connections.
6 Request flows
Search Goa, 3 nights, 2 guests
- 1Search service queries Elasticsearch: geo filter on the Goa area or map box, guests <= max_guests, amenity filters, and availability flags for each of the 3 nights.
- 2Elasticsearch returns ~1,000 candidates ranked by a relevance model (price, rating, conversion, distance).
- 3For the top ~50 shown on the first page, the availability service re-checks live availability and the stay's total price from Redis/Postgres in one batched call.
- 4Properties that became unavailable are dropped; prices are corrected; the page is returned in under 800 ms.
Book a room for 3 nights
- 1Guest clicks Reserve; booking service starts a transaction on the property's shard.
- 2It runs UPDATE inventory SET held = held + 1 WHERE room_type_id = ? AND night IN (n1, n2, n3) AND total - booked - held >= 1 AND NOT closed, and checks that exactly 3 rows were updated; otherwise it rolls back and returns 'sold out'.
- 3It inserts a hold (expires in 10 minutes) and a booking row (status pending_payment) with the Idempotency-Key, then commits.
- 4Guest pays; on payment success (webhook verified), a transaction moves held → booked for those nights and sets the booking confirmed, writing an outbox event.
- 5BookingCreated flows through Kafka to the partner notifier, which pushes it to the hotel's channel manager, and to email/WhatsApp confirmation.
- 6If payment fails or the hold expires, the sweeper releases held units and marks the booking expired.
Hotel updates availability from its channel manager
- 1The hotel sold its last deluxe room on another site; its channel manager calls PUT /inventory for that night with total = booked (or closed = true).
- 2Partner API validates and writes the inventory row with a version check.
- 3CDC streams the change to the search indexer, which updates Elasticsearch within seconds.
7 Deep dives
Inventory model and atomic multi-night booking
The natural unit of hotel inventory is a room type on a night, not a specific room: a hotel says it has 12 deluxe rooms, and assigns room 304 at check-in. So store one row per (room_type_id, night) with total, booked and held counts. Homes on Airbnb are the special case where total = 1. A stay of N nights must reserve all N rows or none. Keep all inventory of one property on one shard, so a booking is a local transaction, not a distributed one. Inside it, a single conditional UPDATE over the N night rows with the check total - booked - held >= rooms, then verify the affected row count equals N. This is safe under concurrency because each row update takes a row lock and re-evaluates the condition; two guests racing for the last room cannot both succeed. Avoid read-then-write in application code without locks. Holds with a short expiry protect the payment window; a sweeper releases expired holds, and confirmation converts held to booked. An Idempotency-Key on the booking request makes retries safe. For hot properties during peak, contention is per row and short, so throughput is fine; no global locks are needed.
Search with dates: the hardest read
Searching 'available in Goa from 20-23 Dec' needs geo, filters and per-night availability together. Pure SQL across billions of inventory rows is too slow for a geo search, and Elasticsearch is not a source of truth. The common approach is a denormalised availability index: for each room type, store availability bits or minimum available count for the next ~180-365 nights, plus the lowest nightly price, inside the property document (or a side index joined by property_id). A query checks the nights of the stay with a script or pre-computed fields. CDC from the inventory tables updates these fields within seconds. Because the index can be slightly stale and prices depend on the exact stay (length-of-stay discounts, taxes), re-check the top results against the availability service before showing them, and always check again at booking. Cache popular searches (city + dates) in Redis for a minute. Map search uses geo bounding boxes with a geohash-based cluster aggregation for zoomed-out views. Ranking blends price, rating, distance, and predicted conversion, and must not show unavailable properties, which is the most annoying failure for users.
Channel managers and the double-booking risk
Most hotels sell the same rooms on many sites at once through a channel manager connected to their property management system (PMS). We hold a copy of their availability, not the truth. Two sites can sell the last room within the same few seconds before updates propagate. Mitigations: push our bookings to the channel manager immediately (async but fast, with retries and an outbox), accept availability updates through a high-throughput partner API, and apply updates with version or timestamp checks so an old message cannot overwrite a newer one. Some hotels give each channel a fixed allotment of rooms, which removes the race at the cost of flexibility. When an overbooking happens anyway, the process matters: detect it quickly (reconciliation with the PMS), contact the guest, and 'walk' them to an equal or better hotel at the platform's cost. Say clearly in the interview that our system guarantees no double-booking within our own inventory; across channels, the guarantee depends on the partner, and the design minimises the window.
8 Trade-offs
Per-night rows vs storing bookings and computing availability
Computing availability by counting overlapping bookings per query is flexible but slow and hard to lock correctly. Per-night counter rows make availability a simple read and make booking a conditional update. The cost is many rows (billions), which is fine with sharding and since most are cold.
Holds during payment vs book on payment success
Holds prevent a guest from paying for a room someone else just took, but holds can lock inventory for bots or abandoned checkouts. Use short TTLs (~10 min), per-user hold limits, and release on payment failure.
Eventually consistent search index
Indexing availability in Elasticsearch makes search fast but stale by seconds. Re-checking the top results and the booking-time transaction keeps the user experience right. Trying to make search strongly consistent would make it far too slow.
Pay now vs pay at hotel
Pay-at-hotel converts better in India for some segments but has more no-shows. Require card guarantee or a small prepaid amount for high no-show segments, and let hotels choose policy.
9 Bottlenecks
Holiday search peaks
Long weekends drive 5x search. Scale Elasticsearch replicas, cache popular city-date searches for 30-60 s, and cap the re-check to the first page.
Channel manager update storms
Hotels push rate changes for hundreds of dates at once. Batch updates per property, collapse multiple updates for the same row in the queue, and rate-limit per partner.
Hot property on a sale
A popular resort during a flash deal has many concurrent bookings on the same rows. Row-level locks are short, so throughput is OK; queue requests per property if lock waits grow.
10 Failure handling
Payment succeeds after the hold expired
The confirm transaction tries to book the nights again. If available, confirm; if not, refund automatically and offer alternatives. Holds should be longer than the payment timeout to make this rare.
Partner notification fails
The outbox keeps the event; the notifier retries with backoff. Bookings not acknowledged by the hotel within a few minutes raise an alert, and reconciliation with the PMS catches mismatches.
Search index lag
If CDC lags, search shows stale availability. Re-check and booking-time checks prevent wrong bookings; monitor CDC lag and fail over to a lighter live query if lag is large.
11 What impresses
- Models inventory as counts per room type per night, and homes as the special case of 1.
- Writes the conditional UPDATE and checks the affected row count equals the number of nights.
- Places all of a property's inventory on one shard to avoid distributed transactions.
- Explains that search is stale by design and how re-check and booking-time checks make it safe.
- Brings up channel managers and honestly scopes the double-booking guarantee.
- Mentions the 'walk the guest' business process when overbooking happens.
12 Mistakes
- Locking specific rooms (room 304) instead of room-type counts for hotels.
- Checking availability and booking in separate steps without a transaction or conditional update.
- Treating the search index as the source of truth for availability.
- Ignoring that hotels sell on other sites too.
- Using a distributed lock service for every booking instead of database row locks.
- Forgetting per-night prices and length-of-stay rules when computing the total.
13 Follow-ups
Lessons this design uses
Practice questions
- A travel app books a flight, a hotel and a cab in one checkout, each through a different provider. The hotel booking fails after the flight is confirmed. Walk me through how your system handles it.
- Your bookings are sharded by user_id. Now the operations team needs 'all bookings for hotel X in the last 7 days', and the query is slow because it hits all 64 shards. What do you do?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.