System Design Problem

Design a Hotel Booking System

Commonly Asked By:Booking.comExpediaAirbnbTripAdvisor

Interview Setup

Interview Prompt

Design a hotel booking system for 1M hotels with 50K searches/sec, 5K bookings/sec (25K peak during holidays), and 18.25B bookable room-nights with inventory holds and overbooking policies.

Clarifying Questions (ask before designing)

QuestionWhy it matters
Inventory model: room-night units or physical room assignment at check-in?Room-night is simpler (18.25B units); physical assignment deferred to hotel PMS.
Hold duration during checkout: 10 min or 30 min?Longer hold reduces double-booking risk but locks inventory from other searchers.
Overbooking allowed? What's the walk policy?
  • Airlines overbook 5-15%
  • hotels walk guests to comparable property + compensation.
Cancellation policy: free cancellation until when?Flexible vs non-refundable affects inventory release timing and revenue.

Scope

In scope

  • Variant of ticketing
  • Room availability calendar
  • Overbooking policy
  • Cancellation
  • Price optimization
  • Capacity estimation with shown math

Out of scope (state explicitly)

  • Flight and travel package bundling
  • Property management / housekeeping systems
  • Revenue-management ML model internals

Functional Requirements

Start by asking your interviewer whether dynamic pricing and controlled overbooking are in scope, because they change the architecture significantly. Hotel booking lives or dies on inventory correctness, so clarify double-booking prevention and cancellation policy before listing search filters.

  • Search hotels by location, dates, guests, price range, amenities, star rating
  • View room types, photos, reviews, availability calendar
  • Book room(s): select dates → reserve → pay → confirm
  • Overbooking management: controlled overbooking with walking policy
  • Cancellation with policy enforcement (free cancel before X days)
  • Price management: dynamic pricing based on demand, season, events
  • Loyalty program: points accrual and redemption
  • Calendar-based inventory (each room-night is a separate unit)

Non-Functional Requirements

Your interviewer will stress-test consistency above all else, because zero double-booking is the invariant they will prioritize before search latency. They will also probe holiday peak surge (5x normal) and how you handle stale search results vs authoritative inventory at booking time.

  • Consistency: No double-booking of the same room-night
  • High Availability: 99.99%: booking failures = lost revenue
  • Low Latency: Search < 200ms, booking < 2s
  • Scalability: 1M+ hotels, 100M+ room-nights of inventory

Capacity Estimations

Room-night inventory is your primary storage driver, as 100M hotels x 365 days x room types scales rapidly. Search QPS and booking peak (holiday weekends) determine how much you can cache vs validate live.

MetricCalculationValue
HotelsGiven1M
Rooms (total)Given50M
Bookable room-nights (next 365 days)Given18.25B
Searches / secDerived from daily volume ÷ 86400 (+ peak factor)50K
Bookings / secDerived from daily volume ÷ 86400 (+ peak factor)5K
Peak (holiday season)Given5x normal

Architecture Diagram

In the interview, split search from booking because they operate under different consistency models. Elasticsearch finds candidate hotels by geo and filters, while the inventory service maintains authoritative truth on whether dates from Dec 20 to Dec 25 remain available.

The booking path is transactional: hold with a 15-minute TTL in Redis, charge payment, then atomically confirm with SELECT FOR UPDATE on every room-night row in the date range. Multi-night stays must lock all nights or roll back entirely.

Pricing and overbooking sit off the critical path: dynamic rates update asynchronously, and controlled overbooking is a revenue optimization layered on top of the inventory invariant, rather than a substitute for it.

Loading...

In the room

Ask whether search can show slightly stale availability, because accepting under 1% booking failures from cache lag avoids 2.5M live database queries per second at 50K searches.

Component Deep Dives

Walk through the hold-and-confirm pattern: reserve with a 15-minute TTL, charge payment, and atomically confirm. Multi-night bookings must lock every room-night row in the date range, rolling back the entire reservation on any partial failure.

Search Service and Elasticsearch

Search is read-heavy and cacheable; begin here because it is the user entry point, while making clear that Elasticsearch is not the source of truth for availability.

The Search Service evaluates bounding-box geo queries, full-text hotel name queries, and faceted filters (star rating, amenities, price range). Elasticsearch returns candidate hotels, the Availability Service validates inventory against Redis, the Pricing Service computes dynamic rates, and the service returns sorted results.

Booking Service: Critical Path

The booking service requires careful scrutiny: pessimistic row locks and multi-night atomicity separate a passing design from a staff-level solution.

PostgreSQL ACID transactions with SELECT FOR UPDATE prevent double-booking. Under the hold-and-confirm pattern, the system reserves inventory in a PENDING state with a 15-minute TTL, processes card payment, and transitions to CONFIRMED. Multi-night atomicity guarantees that all room-night rows across the entire date range lock together.

Pricing Service

Dynamic pricing is decoupled from inventory: demand signals feed a pricing engine that updates rates asynchronously without blocking the booking transaction.

Dynamic rates compute as base price x demand_multiplier x season_factor x day_of_week_factor. Ingested demand signals include booking velocity, search volume, competitor rates, and remaining inventory percentage. A centralized pricing engine enforces rate parity across all distribution channels.

Calendar-Based Inventory Model

Hotels operate on pooled inventory rather than seat-level assignment. Each room-night functions as an independent availability counter, simplifying individual row locking but requiring multi-date transactional atomicity.

Unlike ticketing where each seat is unique:
  Hotels have POOLED inventory: "5 Deluxe King rooms available on Dec 20"

Room-Night Inventory:
  hotel_id + room_type + date → available_count

  Hotel ABC, Deluxe King:
  Dec 20: 5 available (of 10 total)
  Dec 21: 3 available
  Dec 22: 0 available (SOLD OUT)
  Dec 23: 7 available

Booking "Dec 20-23" requires ALL 4 nights to have availability
  → Atomic decrement across all 4 dates in one transaction

Overbooking Strategy

Controlled overbooking is industry-standard practice: evaluate historical no-show probabilities and define explicit walk policies to protect guest experience during sold-out nights.

Airlines/Hotels intentionally overbook by 5-10% (data-driven):
  10 rooms, sell 11 reservations
  Historical no-show rate: 15% → expect 1-2 no-shows
  If all 11 show up → "walk" lowest-priority guest to partner hotel

Implementation:
  available_count can go negative (to overbooking limit)
  overbooking_limit = total_rooms x overbooking_factor (e.g., 1.1)
  
  Decision: ML model predicts no-show probability per booking
  - Business traveler, booked recently: low no-show risk
  - Leisure, booked 6 months ago, no prepayment: high no-show risk

API Design

Present search as a cacheable GET and booking as an idempotent POST. Emphasize client-generated reservation tokens and explicit 409 Conflict responses when inventory is exhausted, preventing silent partial bookings.

HTTP
GET  /api/hotels/search?location=NYC&checkin=2026-12-20&checkout=2026-12-25&guests=2
POST /api/bookings          → {hotel_id, room_type, checkin, checkout, guest_info, payment}
GET  /api/bookings/{id}     → Booking details
DELETE /api/bookings/{id}   → Cancel
GET  /api/hotels/{id}/availability → start=...&end=...

Common Error Responses

400 Bad Request: invalid input, missing required fields, or malformed JSON payload
401 Unauthorized: missing or invalid authentication token or API key
403 Forbidden: authenticated caller lacks required permissions for this resource
404 Not Found: requested resource ID does not exist
409 Conflict: duplicate write or version conflict, retry with a unique idempotency key
422 Unprocessable Entity: syntactically valid request failed semantic business validation
429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header
500 Internal Error: unexpected server failure, retry safely with an idempotency key
503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff
402 Payment Required: account balance or payment method has insufficient funds
502 Bad Gateway: payment gateway provider timeout, poll transaction status endpoint

Data Model

PostgreSQL

SQL
CREATE TABLE room_inventory (
    hotel_id     UUID, room_type TEXT, date DATE,
    total_rooms  INT, booked_rooms INT, overbooking_limit INT,
    price_cents  INT,
    PRIMARY KEY (hotel_id, room_type, date)
);

CREATE TABLE reservations (
    reservation_id UUID PRIMARY KEY,
    hotel_id UUID, room_type TEXT,
    guest_id UUID, checkin DATE, checkout DATE,
    status TEXT,  -- PENDING|CONFIRMED|CANCELLED|CHECKED_IN|COMPLETED
    total_price_cents INT, payment_id UUID,
    cancellation_policy TEXT,
    created_at TIMESTAMPTZ
);

Booking Transaction: Race Condition Prevention

SQL
BEGIN;
-- Lock all room-night rows for the date range
SELECT booked_rooms, total_rooms, overbooking_limit
FROM room_inventory
WHERE hotel_id = $1 AND room_type = $2 AND date BETWEEN $3 AND $4
FOR UPDATE;

-- Check ALL dates have availability
-- If any date has booked_rooms >= overbooking_limit → ROLLBACK

UPDATE room_inventory SET booked_rooms = booked_rooms + 1
WHERE hotel_id = $1 AND room_type = $2 AND date BETWEEN $3 AND $4;

INSERT INTO reservations (...) VALUES (...);
COMMIT;

Fault Tolerance

  • Payment failure after inventory reserved: Hold for 15 minutes with automatic inventory release if payment is not completed.
  • Double-booking prevention: Use SELECT FOR UPDATE within transaction isolation to serialize concurrent bookings on the same room-night.
  • Search-to-book staleness: Availability shown in search results may lag by seconds, so the system executes an authoritative check at booking time.
  • Rate parity: Room prices must remain consistent across distribution channels, enforced by a centralized pricing service.

Additional Considerations

Comparison with Event Ticketing Systems

While an event Ticketing System manages discrete physical seat units where each seat is unique and overbooking is strictly prohibited, hotel booking operates on pooled room-night inventory across date ranges with controlled overbooking.

Ticketing: Each seat is unique → seat-level locking
Hotel: Pooled inventory → count-based → simpler locking
Ticketing: One event, one time → no date-range complexity
Hotel: Multi-night stays → must lock ALL dates atomically
Ticketing: No overbooking (each seat is physical)
Hotel: Controlled overbooking is standard industry practice

Interview Walkthrough

  • 25-minute cut

    Skip arch50/arch75 depth unless staff.

    • Room-night inventory (8 min)
    • Hold with 15-min TTL at checkout (9 min)
    • Row lock on confirm (8 min)
  • Pooled inventory model: guests book a room type (such as Deluxe King for 3 nights) rather than a specific room, deferring physical room assignment to check-in.
  • Controlled overbooking with no-show prediction: cap the overbooking ratio and reconcile with walk-in and no-show statistics daily.
  • Atomic booking via SELECT FOR UPDATE on room_inventory rows across the date range to prevent double-booking under concurrent requests.
  • Search displays eventually consistent availability, while final atomic inventory verification occurs at booking time.
  • Payment authorization holds use a 15-minute TTL to automatically release inventory if checkout is abandoned.
  • Rate parity across OTAs requires a centralized pricing service to ensure the same room displays identical pricing across channels.
  • Common pitfall: decrementing inventory in the search index at query time, where race conditions between search and book cause double-bookings.

Engineering Trade-offs

Scalability: Sharding Strategy

Shard by hotel_id:
  room_inventory: Shard key = hotel_id
  Booking transaction: hits ONE shard (single-shard ACID transaction)
  1M hotels / 16 shards = ~62,500 hotels per shard
  
  Within each shard: partition room_inventory by date range
    PARTITION BY RANGE (date), 1 month per partition

Search (cross-shard):
  Step 1: Elasticsearch returns matching hotels (not sharded by hotel)
  Step 2: Group hotels by shard → fan-out availability queries
  Step 3: Merge results → return to client with prices

Optimization: Redis cache per hotel with availability bitmap
  Key: avail:{hotel_id}:{room_type}:{month}
  Value: bitmask (1 bit per day, 1=available, 0=booked)

Race Condition: Isolation Levels

READ COMMITTED + FOR UPDATE is sufficient since room_inventory rows are pre-created. No INSERTs during booking: only UPDATEs. SERIALIZABLE not needed.

Pooled Inventory vs Named-Room Assignment

Pooled: "5 Deluxe King rooms available": any of the 5 rooms. Simpler booking logic, flexible. Named: Room 401 tracked individually. Industry practice: Pooled inventory for booking, named assignment at check-in. Exception: luxury hotels sell specific rooms.

Eager Payment vs Lazy Payment

Hybrid (industry standard): An authorization hold at booking validates the card. A no-show fee charges 1 night if the guest does not cancel within the policy window. Non-refundable rates charge immediately in exchange for a discounted price.

Search Staleness vs Real-Time Availability

Problem: Search shows "5 rooms available" → user clicks → booking fails

Option 1: Real-time availability on every search result
  → 50K searches/sec x 50 hotels = 2.5M DB queries/sec → DB dies

Option 2: Cached availability with staleness (Recommended)
  Redis bitmap per hotel, refreshed every 30 seconds
  Final check at booking time (authoritative DB with FOR UPDATE)
  Acceptable UX: < 1% of booking attempts fail due to staleness

Option 3: Pessimistic display (show fewer rooms than available)
  Cache shows "available" only if real availability > 2

💬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...