Hotel Reservation System
A hotel reservation system is a classic yet challenging distributed system problem. While the core user experience—searching hotels and booking rooms—appears simple, the underlying system must handle high traffic, low-latency search, strong consistency for bookings, and horizontal scalability, all while ensuring that critical failures such as overbooking never occur.
This article presents a verbose, end-to-end system design for a hotel reservation platform, explaining requirements, entities, APIs, and the architectural decisions behind them.
Functional Requirements
Functional requirements define what the system should do from the perspective of different users.
Admin Capabilities
- Admins can add hotels.
- Admins can add rooms under a hotel.
- Admins can update hotel details.
- Admins can update room details.
- Admins can delete hotels.
- Admins can delete rooms.
- Admins can update room prices for a given date range.
These operations are relatively low-frequency but must be reliable and auditable.
Customer Capabilities
- Customers can search hotels and rooms based on:
- Location
- Ratings
- Room types
- Price range
- Availability for selected dates
- Customers can book hotel rooms.
- Customers can list all their bookings.
Search and booking are the most critical customer-facing flows and must be fast and reliable.
Low-Priority Requirements
These features improve user experience but are not essential for the core system:
- Amenities for hotels and rooms
- Customer reviews and ratings for hotels and rooms
- A notification system (email, SMS, push notifications)
- Hotel recommendations
These can be built as independent services and added incrementally.
Non-Functional Requirements
Non-functional requirements define how the system behaves under load, failures, and scale.
Consistency and the CAP Theorem
Different parts of the system make different trade-offs between consistency, availability, and partition tolerance.
Adding Hotels and Rooms
- Eventual consistency is acceptable.
- New hotels or rooms may take time to appear in search results.
- This prioritizes scalability and availability.
Search
- Search must have low latency.
- It must be highly available.
- It can tolerate slightly stale data.
- It uses denormalized, read-optimized data.
Booking
- Booking requires strong consistency.
- It must prevent overbooking.
- Availability must always be accurate.
This separation allows the system to optimize each workflow independently.
Scalability
- The system must handle very high read traffic for search.
- It must scale horizontally.
- Services should be stateless wherever possible.
Idempotency
- Booking APIs must be idempotent.
- This prevents duplicate bookings caused by retries or network failures.
- It is achieved using idempotency keys and unique constraints.
Core Entities
The system revolves around the following core entities:
Hotel
Represents a physical hotel and contains metadata such as name, address, category, amenities, and description.
Room
Belongs to a hotel and represents either a specific room or a room type. It includes capacity, pricing, and amenities.
Customer
The end user of the system, who can search hotels, make bookings, and leave reviews.
Admin
Manages hotels, rooms, and pricing.
Search
A logical component responsible for querying hotels and rooms efficiently.
Booking
Represents a confirmed reservation and must always remain consistent.
Payment
Handles financial transactions and integrates with external payment gateways.
API Design
Clear API boundaries help maintain scalability and clarity.
Admin APIs
Create Hotel
POST /hotels
{
"name": "Hotel Paradise",
"category": "Luxury",
"amenities": {},
"address": "Goa, India",
"owner": "Paradise Group",
"description": "A beachside luxury hotel"
}
Create Room
POST /hotels/{hotelId}/rooms
{
"roomNumber": "101",
"roomType": "Deluxe",
"amenities": {},
"floor": 1
}
Delete Hotel
DELETE /hotels/{hotelId}
Delete Room
DELETE /hotels/{hotelId}/rooms/{roomId}
Update Hotel
PUT /hotels/{hotelId}
Update Room
PUT /hotels/{hotelId}/rooms/{roomId}
Update Room Price for a Date Range
PUT /hotels/{hotelId}/rooms/prices
{
"room_type_id": "deluxe_101",
"start_date": "2025-07-01",
"end_date": "2025-07-07",
"price": 4500
}
Pricing is time-dependent and should be modeled separately from room metadata.
Customer APIs
Search Hotels
GET /hotels
Query parameters include:
searchtext, city, price_min, price_max, number_of_rooms, number_of_guests, cursor, size
Search is optimized for read-heavy traffic and is typically backed by a search engine.
Create Booking
POST /bookings
{
"hotelId": "h123",
"checkInDate": "2025-07-01",
"checkOutDate": "2025-07-05",
"roomType": "Deluxe",
"numberOfRooms": 2,
"guestName": "John Doe",
"numberOfGuests": 4,
"paymentInfo": {}
}
Booking creation must atomically:
-
Validate availability
-
Reserve rooms
-
Process payment
-
Confirm the booking
List Customer Bookings
GET /bookings?customerId={id}
Add Review
POST /reviews
{
"booking_id": "b456",
"hotel_id": "h123",
"room_type_id": "deluxe_101",
"rating": 4.5,
"comment": "Great stay, excellent service!"
}
High-Level Design
Scale Calculation
Before designing the architecture of a hotel reservation system, it is critical to estimate scale. Scale calculations help us understand expected traffic, storage requirements, and system bottlenecks. This section walks through a verbose and structured scale estimation, based on realistic assumptions for a global platform.
User Assumptions
We start by defining the size of the user base and their behavior.
- Total registered users: 500 million
- Daily active users (DAU): 5 million
Daily active users represent users who interact with the system on a given day and are the primary drivers of traffic.
User Activity Assumptions
Each active user performs multiple actions per day:
- Searches: 10 searches per user per day
- Hotel detail views: 5 hotel listing views per user per day
- Bookings: 1 million total bookings per day (system-wide)
These actions translate directly into read and write traffic on the system.
Traffic Estimation
Queries Per Second (QPS)
QPS represents read-heavy traffic, such as search and hotel detail views.
Search QPS
- Searches per user per day: 10
- Daily active users: 5 million
Total searches per day:
To convert daily traffic to per-second traffic, we assume:
Search QPS:
Hotel Listing View QPS
- Hotel detail views per user per day: 5
- Daily active users: 5 million
Total hotel views per day:
Hotel detail view QPS:
Total QPS
The system’s total read traffic is the sum of search and hotel detail views:
This number is critical for designing:
- Search services
- Caching layers
- Load balancers
Transactions Per Second (TPS)
TPS represents write-heavy, strongly consistent operations, primarily bookings.
Booking TPS
- Total bookings per day: 1 million
Booking TPS:
So:
Even though TPS is much lower than QPS, booking operations are far more complex and require:
- Strong consistency
- Inventory locking
- Idempotency
- Payment coordination
Storage Estimation
Storage calculations help determine database sizing, replication strategies, and cost.
User Storage
- Total users: 500 million
- Storage per user: 1 KB
Total user storage:
This includes:
- Profile data
- Preferences
- Authentication metadata
Booking Storage
- Bookings per day: 1 million
- Bookings per year:
- Bookings over 5 years:
- Storage per booking: 2 KB
Total booking storage:
Bookings form one of the largest and most critical datasets and often require long-term retention.
Hotel and Listing Storage
- Total hotel listings: 10 million
Metadata Storage
- Average metadata size per listing: 5 KB
Total listing metadata storage:
Image Storage
- Average photos per listing: 10
- Average photo size: 1 MB
Total image storage:
Images dominate storage requirements and are typically stored in:
- Object storage (S3-like systems)
- CDN-backed infrastructure
Deep Dive: Data Model
-
Hotel – Stores core property information such as name, description, rating, category, ownership, and address reference.
-
Address – Captures normalized location details including city, state, country, and geo-coordinates for mapping and proximity queries.
-
Room_Type – Defines logical room categories (e.g., Deluxe, Suite) with shared attributes like capacity and bed configuration.
-
Room – Represents physical room inventory mapped to a room type for granular tracking and management.
-
Amenities – Maintains a master catalog of facilities or features that can be associated with hotels or room types.
-
Hotel_Amenities – Maps amenities to hotels, enabling flexible many-to-many relationships without schema changes.
-
Room_Amenities – Associates amenities with room types, allowing different room categories to expose distinct features.
-
Rates – Stores date-aware pricing, discounts, currency, and refund policies to support dynamic pricing strategies.
Deep Dive: Admin Flow
Hotel administrators interact with the system through privileged workflows that modify core business data. These operations are write-heavy, consistency-sensitive, and directly influence downstream systems such as search and pricing.
Typical admin actions include:
- Creating and updating hotels
- Managing room types and rooms
- Updating dynamic room rates
All admin requests enter through the API Gateway:
Admin → API Gateway → Hotel Service
The API Gateway enforces authentication, authorization, and request validation, ensuring only authorized owners or operators can modify hotel data.
The Hotel Service acts as the primary orchestration layer for administrative operations. It validates business rules, persists changes to the primary SQL database, and coordinates with specialized services when required.
Static files, such as hotel/room images, can be stored in object storage and a CDN for faster access.
Room prices for given dates and room availability can also be updated by admins through the Hotel Service.
After successful writes, domain events are published to the Inbound Topic so that downstream consumers can consume them:
Hotel Service / Rate Service → Inbound Topic
This event-driven design ensures that write operations remain fast while allowing downstream search and read models to remain eventually consistent.
Admin flows prioritize strong consistency at the database level, while propagation to read-optimized systems (search, cache) occurs asynchronously for scalability and fault isolation.
When we need to scale pricing updates, the Hotel Service can delegate to the Rate Service, which owns pricing logic and writes to the rates table. This separation isolates frequently changing rate data from relatively static hotel metadata.
Room availability can also be handled by a separate Inventory Service when we have complex reservation logic or want to support overbooking, etc.
Deep Dive: Search in a Hotel Reservation System
Search is the most frequently used and latency-sensitive component of a hotel reservation platform. User perception of system quality is heavily influenced by search speed, relevance, and flexibility.
Hotel searches are intent-rich and multi-dimensional. Queries often combine multiple constraints:
- Location → "near MG Road"
- Price → "under 5000"
- Room Type → "luxury suite"
- Distance → "within 5km"
- Amenities → "rooftop pool"
Example queries:
- "hotels near MG Road with rooftop pool"
- "luxury suite in Goa under 5000"
- "hotels within 5km of Bangalore airport"
Search Flow
Search uses eventual consistency:
- Slightly stale data is acceptable.
- This improves scalability & availability.
- Booking correctness is unaffected.
A typical search request follows this path:
User → Search Page → API Gateway → Search Service → Elasticsearch
- The user submits a query via the Search Page (web/mobile).
- The request passes through the API Gateway.
- The gateway routes it to the Search Service.
- The Search Service queries Elasticsearch.
- Ranked results are returned to the client.
The Search Page acts as the client entry point, capturing user queries and filters such as location, price range, room type, and amenities. It remains intentionally thin, delegating all processing to backend services.
The API Gateway serves as the centralized access layer. It handles authentication, request validation, rate limiting, and routes traffic to the appropriate backend services. This prevents clients from being tightly coupled to internal components.
The Search Service is responsible for interpreting user intent and executing search logic. Its duties include parsing queries, extracting structured filters, constructing Elasticsearch queries, ranking results, and shaping responses for the UI. Importantly, it does not query the primary SQL database.
The Elasticsearch datastore is optimized for search workloads. Hotel and room data is denormalized and indexed to support full-text search, filtering, and geo-spatial queries with low latency. This design avoids expensive joins and reduces query time.
Search Index Update Flow
To maintain search performance and avoid coupling with write-heavy systems, search index updates are handled asynchronously:
Hotel Service → SQL Database → Inbound Topic → Search Consumer → Elasticsearch
- The hotel admin performs CRUD operations via the Hotel Service.
- The Hotel Service writes changes to the primary SQL Database.
- The Hotel Service publishes change events to the Inbound Topic.
- The hotel admin uses the Rate Service to make rate changes (price, discounts, availability, inventory).
- The Rate Service writes updates into the primary SQL Database / Rate Store.
- The Rate Service publishes Rate Update Events to the Inbound Topic.
- The hotel admin also updates inventory details (room availability, etc., for given dates) using the Inventory Service.
- The Inventory Service publishes Inventory Update Events to the Inbound Topic.
- The Search Consumer subscribes to the topic events.
- The Search Consumer transforms and denormalizes the data as needed.
- The Elasticsearch search index is updated asynchronously.
This design ensures:
- No impact on write latency
- Eventual consistency for search data
- Traffic smoothing and replay capability
- Loose coupling between services
- Fault isolation between the core systems and search
The Inbound Topic buffers change events, enabling traffic smoothing, retries, and failure isolation. The Search Consumer processes these events and updates Elasticsearch, ensuring eventual consistency while preserving search performance.
A CDN complements the search experience by serving static assets such as images, significantly reducing backend load and improving page rendering speed.
This component separation allows search to scale independently, maintain high availability, and deliver responses within strict latency targets.
Why Elasticsearch?
Elasticsearch is used because it supports:
- Full-text search
- Fast filtering
- Geo-spatial queries
- Horizontal scalability
Data is denormalized and indexed for low-latency reads.
Deep Dive: Hotel Booking Flow
Booking is the most consistency-critical workflow in a hotel reservation system. Unlike search, booking operations must guarantee correctness, prevent overbooking, and handle concurrent requests safely.
To achieve this, the system introduces dedicated inventory and allocation tables rather than relying solely on a booking record.
Key Supporting Tables
Each table serves a distinct responsibility in preserving correctness and scalability.
- room_availability – Acts as the inventory control layer. It maintains the number of rooms available for a given room type on a specific date.
- room_calendar – Tracks the allocation of physical rooms per date. While availability is tracked at the room type level, real-world operations require assigning specific rooms. This separation allows precise room tracking, housekeeping workflows, and conflict prevention.
- booking – Stores the transactional source of truth for reservations. Booking states typically follow a lifecycle such as:
CREATED → CONFIRMED → COMPLETED
Hotels, rooms / room types, amenities, rates / pricing rules, and room availability / inventory are all highly relational and need strong consistency. Hence, we can keep them in a single database.
We don't necessarily need a separate database for bookings, as modern SQL databases are good enough for our storage and TPS requirements. But considering future scale, we can have a separate booking database if we want.
Booking Workflow
A typical booking request flows through the system as follows:
-
User Initiates Booking
The user selects a hotel, room type, date range, and room count.
The request is sent to the Booking Service. -
Availability Validation
The Booking Service queries the Inventory Service forroom_availabilityfor the requested date range.
The condition enforced is:available_count ≥ requested_rooms -
Rate Validation
Query the Rate Service for the requested date range.
Determine the final price (base price, discounts, currency rules). -
Atomic Inventory Update
To prevent race conditions, inventory updates are executed atomically:UPDATE room_availability SET available_count = available_count - N WHERE available_count ≥ N;A failure indicates a concurrent booking conflict.
-
Booking Record Creation A new entry is inserted into the booking table with status CREATED.
-
Payment Processing
The Booking Service interacts with a Payment Service to make the actual payment (with payment information received from the user's client, such as a card, bank account, etc.).
Payment success → booking_status is changed to CONFIRMED
Payment failure/timeout → booking_status is changed to PAYMENT_FAILED, and the inventory is restored
-
Room Allocation Confirmed bookings result in entries in room_calendar, assigning physical rooms.
-
Cancellation Handling Cancellation reverses the prior operations:
- Increment room_availability
- Update room_calendar
- Update booking_status
How Long Should the Booking Service Wait for Payment Confirmation?
Once a booking entry is created with status CREATED, the system transitions it to PENDING_PAYMENT and waits for payment confirmation. This waiting period must be strictly time-bound to prevent indefinite inventory locking.
In practice, most booking systems use a timeout of 5–15 minutes, commonly around 10 minutes.
Why Not Wait Longer?
- Rooms remain locked but unsold, reducing inventory utilization.
- Search results become inaccurate due to stale reservations.
- High-demand scenarios suffer from artificial scarcity.
Why Not Make It Too Short?
- Legitimate users may fail to complete payment in time.
- Payment failures increase, and the user experience suffers.
- Booking abandonment rates rise.
If payment is not confirmed within the timeout window, the booking is automatically expired, and reserved inventory is released, ensuring system correctness and availability integrity.
Usually, we create a payload in a cache (for example, Redis) with a timeout (TTL). If the payment succeeds before that timeout, we are good. Otherwise, we need to reverse the reservation (restoring the inventory).
Deep Dive: Separate Booking View Service
A dedicated Booking View Service is often introduced to serve user-facing booking queries without impacting core transactional workflows.
Why Separate It?
- Read / Write Isolation – Booking systems are write-heavy, while user views are read-heavy; separation prevents contention.
- Optimized Read Models – Views can use denormalized, query-friendly structures tailored for UI needs.
- Performance Stability – High read traffic (dashboards, history pages) does not degrade booking or payment operations.
- Scalability – Read services can scale independently from booking logic.
- Flexibility – Enables aggregation of data from multiple sources (booking, payment, refunds) without complicating core services.
This pattern follows the common principle of CQRS (Command Query Responsibility Segregation), improving responsiveness and system resilience.
Even if the Booking View Service reads from the same Booking DB, separating it helps isolate read-heavy traffic from write-critical booking operations, protecting latency, stability, and scalability.
Archive Old Bookings
Old bookings are typically archived to reduce load on the primary database, improve query performance, and keep transactional tables small and efficient.
We can have a separate Archival Service that clears out old bookings (after, let's say, 3 months) and puts them in an archival database (Cassandra might be a good choice here).
If a user wants to view such old booking details, they can be fetched from the archival database via the Booking View Service.
Analytics & Notification Service
Every large-scale system should have analytics and notification systems. In particular, booking notifications about status changes must be sent to customers.
We can have a Search Topic and a Booking Topic, to which the Search Service and Booking Service publish search- and booking-related events. The Analytics and Notification Services can read the events from the respective topics.
