Hotel Booking System Design: Search, Inventory & Reservation Flow
See a support team verify a case before taking customer action.
The sample reconciles support, order, and payment evidence, drafts a resolution, and pauses before the final action.
Then your own case — no account, no API key
Hotel booking platforms like Booking.com and Expedia handle millions of searches per day, each querying availability across thousands of properties in real time. A single property might receive hundreds of concurrent booking attempts during peak season. Designing a system that returns accurate availability, prevents double-bookings, and supports dynamic pricing is a rich system design challenge.
High-Level Architecture#
A hotel booking system consists of several core services:
- Search & Availability Service — Handles user queries for hotels by location, dates, guests, and filters.
- Inventory Service — Manages room types, counts, and real-time availability.
- Reservation Service — Orchestrates the booking lifecycle from hold to confirmation.
- Pricing Engine — Computes dynamic rates based on demand, seasonality, and competitive data.
- Payment Service — Handles charges, refunds, and integration with payment gateways.
- Review Service — Collects and surfaces guest reviews and ratings.
- Notification Service — Sends confirmation emails, reminders, and cancellation notices.
Search & Availability#
Search is the most read-heavy operation. Users search by city, dates, guest count, and apply filters like star rating, amenities, and price range.
Data Model#
Each property has multiple room types (e.g., Standard King, Deluxe Suite). Each room type has a daily inventory count — the number of rooms available on a given date.
Property: { id, name, location, star_rating, amenities[] }
RoomType: { id, property_id, name, capacity, base_price }
Inventory: { room_type_id, date, total_rooms, booked_rooms }
Search Flow#
- Geo query — Filter properties by location using a geospatial index (PostGIS, Elasticsearch geo queries).
- Availability check — For the requested date range, verify that
total_rooms - booked_rooms > 0for at least one room type on every night of the stay. - Filter & rank — Apply user filters, then rank by relevance (price, rating, distance, promoted listings).
Caching Strategy#
Search results are highly cacheable. Use a multi-layer cache:
- CDN/edge cache for popular city + date combinations (TTL: 5-10 minutes).
- Application cache (Redis) for property metadata and pre-computed availability snapshots.
- Real-time availability is checked at booking time, not search time — stale search results are acceptable as long as the booking flow validates freshly.
Room Inventory Management#
Inventory is the heart of the system. Each room type has a per-date allocation that must be tracked accurately.
Inventory Table Design#
room_inventory (
room_type_id UUID,
date DATE,
total_rooms INT,
booked_rooms INT,
blocked_rooms INT, -- maintenance, VIP holds
PRIMARY KEY (room_type_id, date)
)
Hotels can bulk-update inventory through an admin portal. Changes propagate to the search index asynchronously via CDC (Change Data Capture) or event streams.
Overbooking Strategy#
Some hotels intentionally overbook by 5-10%, similar to airlines, expecting cancellations and no-shows. The system supports an overbooking margin per room type that allows booked_rooms to exceed total_rooms within a configured threshold.
Double-Booking Prevention#
This is the single most critical correctness requirement. Two users must never confirm the same last room.
Optimistic Locking with Version Check#
UPDATE room_inventory
SET booked_rooms = booked_rooms + 1, version = version + 1
WHERE room_type_id = ? AND date = ? AND booked_rooms < total_rooms AND version = ?
If the update affects zero rows, the room was taken — the booking attempt fails gracefully.
Distributed Lock Approach#
For multi-date reservations (e.g., 5-night stay), all dates must be atomically reserved. Use a distributed lock (Redis SETNX or Redlock) keyed on room_type_id to serialize concurrent booking attempts for the same room type.
The lock is held only during the inventory decrement — typically under 50 milliseconds — then released.
Two-Phase Reservation#
- Hold phase — Temporarily reserve inventory for 10-15 minutes while the user completes payment. Decrement
booked_roomsand set a TTL. - Confirm phase — On successful payment, the reservation becomes permanent.
- Release phase — If the hold expires without payment, a background job releases the inventory.
Pricing Engine#
Hotel pricing is far more complex than a static rate card.
Dynamic Pricing Factors#
- Demand-based — Higher occupancy drives higher prices. When a property hits 80% occupancy for a date, prices increase by a configurable multiplier.
- Seasonal — Peak seasons (holidays, local events) have pre-configured rate adjustments.
- Day-of-week — Weekend vs. weekday rates.
- Length of stay — Discounts for longer stays (e.g., 7+ nights gets 15% off).
- Competitive — Scrape or ingest competitor rates to stay competitive.
- Last-minute — Prices may drop as the check-in date approaches if occupancy is low.
Pricing Computation#
Prices are pre-computed nightly by a batch job and stored in a rate table:
rate_calendar (
room_type_id UUID,
date DATE,
computed_rate DECIMAL,
currency VARCHAR(3),
last_updated TIMESTAMP
)
Real-time adjustments (flash sales, manual overrides) are applied as overlays on top of pre-computed rates.
Reservation Flow#
The end-to-end booking flow:
- Search — User finds a property and selects dates.
- Room selection — User picks a room type and rate plan.
- Hold inventory — System places a temporary hold.
- Guest details — User enters name, contact, special requests.
- Payment — User provides payment information; system authorizes (not captures) the charge.
- Confirmation — On successful auth, inventory hold becomes permanent, confirmation email is sent.
- Capture — Payment is captured closer to check-in date (or immediately, depending on policy).
Reservation States#
PENDING -> HELD -> CONFIRMED -> CHECKED_IN -> COMPLETED
With branches: HELD -> EXPIRED, CONFIRMED -> CANCELLED, CONFIRMED -> NO_SHOW
Payment Integration#
Integrate with Stripe, Adyen, or similar gateways. Key patterns:
- Authorize at booking, capture at check-in — Reduces refund friction for cancellations.
- Idempotency keys — Every payment request uses an idempotency key to prevent duplicate charges on retries.
- Multi-currency — Store prices in the property's local currency; convert at display time using daily exchange rates.
- PCI compliance — Never store raw card data. Use tokenization via the payment gateway.
Cancellation Policies#
Hotels define cancellation policies per rate plan:
- Free cancellation — Full refund if cancelled more than 24-48 hours before check-in.
- Non-refundable — No refund; typically offered at a discounted rate.
- Partial refund — First night charged, remaining nights refunded.
The system enforces policies automatically:
cancellation_policy (
rate_plan_id UUID,
deadline_hours INT, -- hours before check-in
penalty_type ENUM('none', 'first_night', 'full_stay', 'percentage'),
penalty_value DECIMAL
)
When a cancellation is processed, the system calculates the refund amount, initiates the refund via the payment gateway, and releases the inventory.
Review System#
Post-checkout, guests receive a review prompt. Reviews include:
- Overall rating (1-5 stars).
- Category ratings — Cleanliness, location, service, value.
- Text review — Free-form feedback.
- Photos — Optional image uploads.
Moderation Pipeline#
- Automated screening — Flag reviews with profanity or spam signals.
- Verified stay check — Only guests with a completed reservation can leave reviews.
- Hotel response — Properties can respond publicly to reviews.
Aggregation#
Property ratings are pre-computed and updated asynchronously. A weighted average gives more weight to recent reviews. The aggregated score is denormalized into the search index for fast retrieval.
Scaling for Peak Seasons#
Peak periods (New Year's Eve in Times Square, summer in the Mediterranean) can see 10x normal traffic.
Read Path Scaling#
- Elasticsearch for search with horizontal scaling — add read replicas during peak.
- CDN caching for property pages and images.
- Pre-computed availability snapshots refreshed every few minutes rather than querying live inventory on every search.
Write Path Scaling#
- Partition inventory by property — Each property's inventory is independent, enabling horizontal partitioning.
- Queue-based booking — During extreme spikes, booking requests enter a queue and are processed serially per room type, preventing lock contention.
- Auto-scaling — Reservation and payment services scale horizontally behind a load balancer.
Graceful Degradation#
- Stale search results — Show cached results with a "prices may have changed" disclaimer.
- Waitlists — When a property sells out, offer to notify users if a room becomes available.
- Rate limiting — Protect inventory writes from bot traffic and scraping.
Key Design Trade-offs#
| Decision | Trade-off |
|---|---|
| Optimistic locking vs. distributed locks | Simpler for single-date bookings vs. needed for multi-date atomicity |
| Pre-computed pricing vs. real-time | Faster reads but potential staleness vs. always accurate but slower |
| Authorize vs. capture at booking | Better UX for cancellations vs. guaranteed revenue |
| Overbooking margin | Higher revenue vs. risk of walking guests |
| Eventual consistency for search | Better performance vs. showing sold-out rooms briefly |
Conclusion#
A hotel booking system is a study in balancing consistency and performance. The inventory subsystem demands strong consistency to prevent double-bookings, while the search subsystem thrives on eventual consistency and aggressive caching. Dynamic pricing, cancellation policies, and peak-season scaling add layers of complexity that make this a compelling system design problem.
The key insight is separating the read path (search) from the write path (booking) — optimizing each independently while keeping them loosely coupled through event-driven updates.
Ready to practice system design problems like this with AI-powered feedback? Try it at codelit.io.
This is article #201 in the Codelit system design series.
See a support team verify a case before taking customer action.
The sample reconciles support, order, and payment evidence, drafts a resolution, and pauses before the final action.
Then your own case — no account, no API key
Try these templates
Uber Real-Time Location System
Handles 5M+ GPS pings per second using H3 hexagonal geospatial indexing.
6 componentsE-Commerce Checkout System
Production checkout flow with Stripe payments, inventory management, and fraud detection.
11 componentsAirbnb-like Booking Platform
Property rental marketplace with search, booking, payments, and reviews.
10 componentsContinue learning
Go deeper on system design
As an Amazon Associate I earn from qualifying purchases. Codelit may receive a commission at no extra cost to you.
Building Microservices
Sam Newman · 2021
600 pages on splitting a monolith: service boundaries, data decomposition, migration order.
Designing Data-Intensive Applications
Martin Kleppmann, Chris Riccomini · 2026
Traces replication, consistency and consensus end to end, with the failure modes behind each choice.
3.6 (196)#1 in Data MiningSoftware Architecture: The Hard Parts
Neal Ford, Mark Richards, Pramod Sadalage, Zhamak Dehghani · 2021
Works through service granularity, distributed data and saga patterns as full trade-off case studies.
4.6 (731)#2 in Software Design ToolsAccelerate
Nicole Forsgren, Jez Humble, Gene Kim · 2018
The statistical method behind the four delivery metrics, and the survey questions used to get them.
4.4 (3.5k)