System Design Interview
Design a Hotel Booking System (Booking.com)
Inventory management, double-booking prevention, and search across 1M+ properties
The Interview Question
"Design a hotel booking system. Users can search for available hotels by location and date, view room availability, and make reservations. The system must prevent double-booking."
This question is fundamentally about inventory consistency: how do you ensure two users cannot book the same room on the same night, even under high concurrency? It also tests search design — availability search across millions of properties with dynamic inventory is not a simple database query.
Step 1: Requirements
Functional
- Search hotels by location, check-in / check-out dates, number of guests
- View available room types and prices for a property
- Reserve a room: hold it for 10 minutes while the user completes payment
- Confirm booking after successful payment
- Cancel a booking (full refund if > 24 hours before check-in)
- View booking history
Non-functional
- 1 million hotels, 10 million rooms globally
- 50,000 search requests/second (read-heavy)
- 5,000 booking attempts/second
- Zero double-bookings — this is a hard correctness requirement
- Search results return in under 1 second
Step 2: The Double-Booking Problem
This is the core challenge. Two users search at 09:00:00, both see "1 room available", both click "Book" at 09:00:01. Without concurrency control, both succeed and the same room is sold twice.
Option A: Pessimistic Locking
When User A starts the booking process for Room 101 on Night X, lock that row in the database. User B's request is blocked until A either completes or abandons.
BEGIN TRANSACTION
SELECT * FROM room_availability
WHERE room_id = 101 AND date = '2026-06-15'
FOR UPDATE ← acquires row-level lock
-- User A checks availability, fills payment form (up to 10 minutes)
-- Row is LOCKED for the entire duration
UPDATE room_availability SET status = 'booked' WHERE room_id = 101 AND date = '2026-06-15'
COMMITProblem: User A holds the lock while filling in their payment details for 5 minutes. User B waits 5 minutes or gets a timeout error. At 5,000 booking attempts/second this creates a massive queue of blocked transactions.
Option B: Optimistic Locking
Don't lock the row. Instead, when you write, check that nothing has changed since you read.
READ:
SELECT room_id, status, version FROM room_availability
WHERE room_id = 101 AND date = '2026-06-15'
→ returns {status: "available", version: 42}
-- User fills in payment form...
WRITE (conditional update):
UPDATE room_availability
SET status = 'booked', version = version + 1
WHERE room_id = 101 AND date = '2026-06-15' AND version = 42
← only succeeds if nobody else updated it
If 0 rows affected: another user got there first → show "no longer available"
If 1 row affected: success → create booking recordOptimistic locking is correct for hotel booking because conflicts are rare — most booking attempts succeed on the first try. The retry only happens when two users compete for the same room simultaneously.
Option C: Reservation with a Hold
The most production-realistic approach: create a temporary hold before payment.
Step 1: User selects room → system creates a HOLD (10-minute TTL)
INSERT INTO holds (room_id, date, user_id, expires_at)
WHERE NOT EXISTS (SELECT 1 FROM holds WHERE room_id = X AND date = Y AND expires_at > NOW())
AND NOT EXISTS (SELECT 1 FROM bookings WHERE room_id = X AND date = Y)
Step 2: User completes payment (within 10 minutes)
→ Convert hold to confirmed booking
→ Delete hold
Step 3: Hold expires without payment
→ Background job deletes expired holds → room becomes available againThis mirrors what Booking.com and Airbnb actually do: a "soft lock" during checkout that expires automatically.
Step 3: Architecture
┌───────────────────────────────────────────────────────────┐
│ Clients │
└───────────────────────┬───────────────────────────────────┘
│
┌─────────▼──────────┐
│ API Gateway │
└──┬──────┬──────────┘
│ │
┌────────────▼──┐ ┌▼───────────────────┐
│ Search Service│ │ Booking Service │
│ │ │ (state machine) │
│ Elasticsearch │ │ + Payment Client │
│ for property │ └──────────┬──────────┘
│ search │ │
└───────────────┘ ┌────────▼──────────┐
│ PostgreSQL │
│ (inventory, │
│ bookings, │
│ holds) │
└───────────────────┘Step 4: Search — Why Two Databases
PostgreSQL (or equivalent RDBMS): stores inventory, bookings, holds, and pricing. Strong consistency required. Search by room_id + date range is a simple indexed lookup.
Elasticsearch: stores property descriptions, location, amenities, star rating — the searchable metadata. Supports:
- Full-text search ("boutique hotel Paris")
- Geo distance queries ("hotels within 2km of Eiffel Tower")
- Faceted filtering (star rating, price range, amenities)
- Returns matching property IDs in milliseconds
The two-step search flow:
1. User searches "Paris hotels, Jun 15-17, 2 guests, max €200/night, ≥4 stars"
2. Elasticsearch query:
→ geo filter: within 20km of Paris centre
→ filter: star_rating >= 4
→ returns: [property_1, property_2, ..., property_200]
3. PostgreSQL availability query (batch):
SELECT property_id, room_type, price FROM room_availability
WHERE property_id IN (property_1, ..., property_200)
AND date BETWEEN '2026-06-15' AND '2026-06-17'
AND status = 'available'
AND guests_capacity >= 2
AND price_per_night <= 200
→ returns: properties that actually have rooms available for those dates
4. Merge Elasticsearch metadata + PostgreSQL availability
Sort by relevance / price
Return top 20 results to userElasticsearch handles the descriptive search. PostgreSQL enforces availability consistency.
Step 5: Booking State Machine
SEARCHED → HOLD_PLACED (10 min TTL)
├─ PAYMENT_PROCESSING
│ ├─ CONFIRMED ──────────────────┐
│ └─ PAYMENT_FAILED → (hold expires) │
└─ HOLD_EXPIRED (user didn't complete) │
│
CONFIRMED ──── CANCELLED (> 24h before check-in, full refund)│
└── CANCELLED (< 24h before check-in, no refund) │
│
COMPLETED ◄───────────────────────────────────────────────────┘
(check-out date passed, booking archived)Each transition is an atomic database update. No transition is allowed to skip a state.
Step 6: Pricing Complexity
Hotel pricing changes dynamically based on: dates, occupancy, seasonality, length of stay, promotions, loyalty tier.
For an interview, acknowledge this complexity without designing a full revenue management system:
Price is calculated at search time by a Pricing Service:
base_price (from hotel's rate table)
× occupancy_multiplier (% rooms booked for that date)
× seasonal_multiplier (Christmas, summer)
× length_of_stay_discount (3+ nights = 5% off)
Price is locked in at the time of hold creation.
If the hold expires, the price may change on re-search.Step 7: Cancellations and Inventory Return
When a booking is cancelled, the room must become available again. This requires:
1. Update booking status to CANCELLED
2. Update room_availability: SET status = 'available' for those dates
3. Initiate refund via payment provider (if eligible)
4. Update Elasticsearch availability index (async, acceptable slight delay)
All of steps 1-3 happen in ONE database transaction.
If any fails, the whole thing rolls back.
Step 4 is asynchronous — Elasticsearch is eventually consistent for search.Step 8: Cost Control
| Area | Optimisation | Why | |------|-------------|-----| | Search | Elasticsearch caches popular search queries | Identical searches (Paris, weekend) served from query cache | | Availability | Redis cache for today+7 days availability | Read-heavy near-future dates cached, DB for far future | | Expired holds | Background cleanup job every minute | Prevents ghost inventory blocking real bookings | | Property images | CDN with long TTL | Property photos rarely change; serve from edge |
What the Interviewer Is Actually Testing
- Do you identify the double-booking race condition immediately?
- Can you compare pessimistic vs optimistic locking and choose appropriately?
- Do you propose a hold / reservation TTL pattern?
- Do you understand why two databases (Elasticsearch + PostgreSQL) are needed for search + availability?
- Is your booking state machine complete and do transitions prevent invalid states?
- Do you handle cancellation as an atomic transaction?
Related Case Studies
Go Deeper
Case studies teach the "what". Our courses teach the "how" — the patterns behind these decisions, built up from first principles.
Explore Courses