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)
| Question | Why 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? |
|
| 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.
| Metric | Calculation | Value |
|---|---|---|
| Hotels | Given | 1M |
| Rooms (total) | Given | 50M |
| Bookable room-nights (next 365 days) | Given | 18.25B |
| Searches / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 50K |
| Bookings / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 5K |
| Peak (holiday season) | Given | 5x 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.
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.
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
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
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 UPDATEonroom_inventoryrows 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
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.