Design Hotel Booking System (Booking.com)

Designing a hotel booking system like Booking.com is a classic read-heavy distributed system problem with strong consistency requirements for bookings. It involves handling search at scale, inventory management, pricing, and transactional booking workflows across millions of properties worldwide.

At its core, the system connects a user searching for hotels with available inventory, applies filters and ranking, and ensures reliable booking without overbooking.

Understanding Requirements

Functional Requirements

1. Users should be able to search hotels by city, dates, guests, and filters.
2. The system should show real-time availability and pricing.
3. Users should be able to book rooms with confirmation.
4. Hotels should manage inventory and pricing.
5. The system should support reviews and ratings.
6. Users should receive notifications for booking status.

Non-Functional Requirements

1. The system must ensure low latency search across large datasets.
2. It should be highly scalable for global traffic.
3. The system must maintain strong consistency for bookings.
4. It should provide high availability.
5. The system must prevent overbooking.

Capacity Estimation

Assume the system operates at a global scale with around 100 million daily active users interacting with approximately 5 million hotels distributed across different regions. This immediately implies a massive dataset with high cardinality across locations, dates, room types, and pricing variations.

Users typically perform multiple searches within a single session, refining filters such as price range, amenities, ratings, and location. As a result, the system must handle hundreds of thousands of search queries per second, each requiring fast retrieval, filtering, and ranking across a large dataset.

On the other hand, booking operations are comparatively fewer but far more critical. With around 1–2 million bookings per day, each booking represents a financial transaction and involves strict correctness requirements.

Unlike search, which can tolerate eventual consistency, booking workflows must ensure that inventory is not oversold and that every confirmed booking is accurate and durable.

This dual nature of workload clearly defines the system characteristics. The system is heavily read-intensive, driven by continuous search queries that require optimized indexing, caching, and denormalized data models for performance. At the same time, it is consistency-critical on the write path, where booking transactions must guarantee correctness through mechanisms like atomic updates, locking, or transactional databases.

Request Flow

1. When the user opens the application, the flow begins with client initialization, where the app loads essential assets, restores the user session, and fetches initial configuration such as user preferences, currency, and locale. This step ensures that subsequent interactions are personalized and context-aware.

2. The user then enters search parameters such as destination (city or hotel), check-in and check-out dates, number of guests, and optional filters like price range or amenities.

3. The search request is sent through the API Gateway, which acts as the entry point for all client communication. It performs authentication checks, applies rate limiting, and routes the request to the appropriate backend service, in this case, the Search Service.

4. The Search Service processes the request by querying an indexed datastore such as Elasticsearch. This index contains denormalized hotel data, enabling fast filtering, geo-based queries, and full-text search without relying on slow relational joins.

5. Once candidate hotels are identified, the system enriches results using cached availability summaries and approximate pricing data from Inventory and Pricing systems. Since fetching fully real-time data for every search result would be expensive at scale, final validation is deferred until booking confirmation.

6. The combined results are then passed through a ranking layer, which orders hotels based on multiple factors such as relevance, price, rating, popularity, and personalization signals. The ranked list is returned to the client, typically with pagination to handle large result sets efficiently.

7. After reviewing options, the user selects a hotel and initiates the booking process. This action triggers a request to the Booking Service, which is responsible for handling transactional workflows and ensuring data consistency.

8. Before confirming the booking, the system performs an inventory lock to temporarily reserve the selected room for the given dates. This prevents other users from booking the same inventory concurrently and avoids overbooking scenarios.

9. Once the inventory is secured, the system proceeds with payment processing, integrating with external payment gateways. Only after successful payment authorization does the system finalize the booking.

10. Finally, the booking is marked as confirmed, and the system triggers notification workflows to inform the user via email, SMS, or push notifications. At the same time, downstream systems such as analytics and reporting may be updated asynchronously through event streams.

Major Components

Client (Web/Mobile)

At the core, the client manages the search UI, where users enter parameters such as destination, dates, and guest count, while also applying filters like price range, ratings, amenities, and sorting preferences. From a system design perspective, the initial application load is optimized using CDNs to serve static assets such as HTML, CSS, JavaScript bundles, and images, ensuring low latency globally. Modern clients also use techniques like code splitting and lazy loading of components so that only critical resources are loaded upfront, reducing time-to-interactive.

The client is responsible for rendering hotel listings, which may include thousands of results. To handle this efficiently, it uses pagination, infinite scrolling, and list virtualization, ensuring that only visible items are rendered in memory. Additionally, results are often fetched in batches using API pagination, and intermediate responses may be cached locally to avoid redundant network calls.

In addition, it integrates map-based visualization, allowing users to view hotels geographically. This requires tight synchronization between backend geo queries and frontend rendering, where map tiles are loaded via CDN-backed providers, and markers are dynamically clustered to reduce rendering overhead. As users pan or zoom, the client triggers incremental API calls instead of full reloads, ensuring smooth interaction.

The client displays pricing and availability information, which is inherently dynamic. To manage this efficiently, the client often relies on cached search responses combined with real-time validation APIs when a user selects a hotel. Techniques like optimistic UI updates and partial component refresh ensure that only affected sections are updated, minimizing re-rendering costs and improving perceived performance.

It also manages the complete booking interaction flow, including room selection, guest details, payment input, and confirmation screens. During this process, the client interacts with multiple backend services in sequence, often using retry-safe APIs and idempotency tokens to handle network failures gracefully. Form validation is partially handled on the client side to reduce unnecessary backend calls, while critical validations are enforced server-side.

Another critical responsibility is maintaining session state, including authentication tokens, user preferences, recently viewed hotels, and in-progress bookings. This state is typically stored using a combination of local storage, secure cookies, and in-memory state management, enabling fast access and seamless transitions across pages or app screens.

For real-time communication, the client listens for asynchronous updates such as booking confirmations, payment status, or price changes. These updates are delivered via push notifications, WebSockets, or lightweight polling depending on the use case.

API Gateway

The API Gateway is responsible for authentication and authorization, where incoming requests are validated using mechanisms such as JWT tokens, API keys, or session cookies. This ensures that only authenticated users can access protected endpoints, and it prevents unauthorized traffic from reaching internal services.

The gateway performs intelligent request routing, forwarding requests to the appropriate downstream services such as Search, Booking, Inventory, or Pricing. This routing is typically based on URL paths, request headers, or versioning, allowing services to evolve independently without impacting clients.

Another critical function is rate limiting and throttling, which protects the system from abuse, traffic spikes, or malicious attacks. By enforcing limits per user, IP, or API key, the gateway ensures fair usage and maintains system stability under high load.

From a system design perspective, the API Gateway also handles request aggregation and transformation, where it may combine responses from multiple services into a single payload to reduce client round trips. It can also normalize request/response formats, enabling backward compatibility and smoother API evolution.

To improve performance, the gateway may implement edge caching for idempotent GET requests, especially for frequently accessed endpoints like search suggestions or static metadata.

Search Service

The Search Service is designed for extremely high read throughput and low latency, as it serves the majority of user traffic in a hotel booking system. Unlike transactional services, it is optimized for fast retrieval, filtering, and ranking over large, denormalized datasets.

At its core, the service uses search-optimized datastores such as Elasticsearch (or OpenSearch), which provide native support for full-text search, faceted filtering, and geo-spatial queries. These systems allow users to search by city, hotel name, landmarks, and apply filters like price range, amenities, ratings, and availability, all within milliseconds.

To achieve this performance, the system maintains a denormalized search index rather than querying normalized relational databases. This index aggregates data from multiple services such as Hotel Metadata, Reviews, and Pricing into a single document per hotel, eliminating the need for runtime joins.

The search index is intentionally highly denormalized because search workloads prioritize low latency over storage efficiency. Duplicating hotel metadata inside Elasticsearch avoids expensive runtime joins across multiple services, allowing queries to execute within milliseconds even at global scale.

Search Index Schema (Elasticsearch)

{
  "hotel_id": 12345,
  "name": "Grand Palace Hotel",
  "city": "Delhi",

  "location": {
    "lat": 28.6139,
    "lon": 77.2090
  },

  "amenities": [
    "wifi",
    "pool",
    "parking"
  ],

  "rating": 4.3,
  "review_count": 1200,

  "price_range": {
    "min": 3000,
    "max": 8000
  },

  "room_types": [
    "deluxe",
    "suite"
  ],

  "availability_summary": true,

  "last_updated": "2026-05-06T10:00:00Z"
}
The location field is indexed using geo-spatial structures, enabling queries like β€œhotels within 5 km.” The amenities and room_types fields support fast filtering, while rating and price_range are used for sorting and ranking.

The Search Service does not own primary data. Instead, it relies on an event-driven pipeline to keep its index updated. Changes from upstream services are published as events and consumed asynchronously.

Consumers process these events, transform them into index documents, and update Elasticsearch. This ensures that the search index remains eventually consistent while maintaining high availability and performance.

When a search request arrives, the service constructs a query combining multiple dimensions such as location, filters, and sorting preferences. Elasticsearch executes this query using inverted indexes and returns a ranked list of hotel IDs along with basic metadata.

In many systems, the Search Service may return lightweight results (hotel IDs and summary fields), and additional details like real-time pricing or availability are fetched from downstream services before sending the final response to the client.

To further improve performance, frequently repeated queries are cached using Redis or edge caches. Cache keys are typically derived from location + dates + filters, and short TTLs ensure that results remain fresh while reducing load on the search cluster.

The search index is sharded across multiple nodes based on hotel_id or geographic regions, allowing horizontal scaling. Replicas ensure high availability and fault tolerance. Query load is distributed across nodes, enabling the system to handle massive traffic spikes.

The key trade-off in this design is eventual consistency. Since the search index is updated asynchronously, there may be slight delays between a data change (e.g., price update) and its reflection in search results. However, this is acceptable because final validation is always performed during booking using strongly consistent services.

Inventory Service

The Inventory Service acts as the source of truth for room availability and is responsible for guaranteeing that no more rooms are sold than actually exist. Its primary responsibility is to maintain accurate, real-time availability per (hotel_id, room_type_id, date) while handling extremely high concurrency during booking spikes.

To achieve a consistent booking experience, the service relies on a combination of strongly consistent storage (RDBMS), atomic updates, and a two-phase reservation (soft lock) mechanism. The schema is designed to explicitly separate available inventory from temporarily reserved inventory, allowing safe coordination between search, booking, and payment flows.
CREATE TABLE inventory (
    hotel_id BIGINT,
    room_type_id BIGINT,
    
    date DATE,
    
    available_rooms INT,
    reserved_rooms INT DEFAULT 0,
    
    updated_at TIMESTAMP,
    
    PRIMARY KEY (hotel_id, room_type_id, date)
);
When a booking is initiated, the Inventory Service does not immediately decrement availability. Instead, it creates a temporary reservation by incrementing reserved_rooms for all requested dates, ensuring that other users cannot take the same inventory while the current user completes payment.
UPDATE inventory
SET reserved_rooms = reserved_rooms + 1
WHERE hotel_id = ?
    AND room_type_id = ?
    AND date BETWEEN ? AND ?
    AND available_rooms - reserved_rooms > 0;
This operation is executed inside a transaction, and the system verifies that all dates were successfully reserved by checking affected rows. If any date fails, the entire transaction is rolled back, ensuring all-or-nothing consistency for multi-day bookings.

Once payment is successful, the reservation is finalized by converting reserved inventory into consumed inventory, ensuring durability of the booking.
UPDATE inventory
SET
    reserved_rooms = reserved_rooms - 1,
    available_rooms = available_rooms - 1
WHERE hotel_id = ?
    AND room_type_id = ?
    AND date BETWEEN ? AND ?;
If the reservation expires or payment fails, the system simply releases the hold by decrementing reserved_rooms. A TTL-based cleanup mechanism ensures that abandoned reservations do not block inventory indefinitely.

To handle high contention, the service may use row-level locking (SELECT ... FOR UPDATE) or rely on optimistic atomic updates. Additionally, data is often partitioned by hotel_id or region to distribute load and reduce contention hotspots.

Inventory changes are published as events, enabling other services like Search to update availability signals asynchronously without compromising write-path consistency.

Booking Service

The Booking Service acts as the transactional orchestrator of the system, coordinating between Inventory, Pricing, and Payment services to ensure that every booking is processed exactly once and in a consistent, fault-tolerant manner.

Its primary responsibility is to manage the end-to-end booking workflow, which includes reservation creation, payment processing, confirmation, and failure handling. To achieve this, it implements a two-phase booking flow built on top of the Inventory Service's reservation mechanism.

In the first phase, the Booking Service creates a reservation record and requests the Inventory Service to place a soft lock on the required rooms. This ensures that inventory is held exclusively for the user during the payment window.
CREATE TABLE reservations (
    reservation_id BIGINT PRIMARY KEY,
    
    user_id BIGINT,
    hotel_id BIGINT,
    room_type_id BIGINT,
    
    check_in DATE,
    check_out DATE,
    
    status TEXT,   -- RESERVED, EXPIRED, CONFIRMED
    
    expires_at TIMESTAMP,
    created_at TIMESTAMP
);
The reservation is associated with a TTL, ensuring automatic expiration if the user abandons the flow. This prevents deadlocks in inventory and improves system fairness.

In the second phase, after successful payment, the Booking Service confirms the booking by converting the reservation into a durable record and triggering inventory finalization.
CREATE TABLE bookings (
    booking_id BIGINT PRIMARY KEY,
    
    reservation_id BIGINT,
    
    user_id BIGINT,
    hotel_id BIGINT,
    room_type_id BIGINT,
    
    check_in DATE,
    check_out DATE,
    
    status TEXT,   -- CONFIRMED, CANCELLED
    
    created_at TIMESTAMP
);
If payment fails or times out, the Booking Service triggers a compensation flow, marking the reservation as expired and releasing the held inventory.

To guarantee correctness under retries and network failures, the Booking Service uses idempotency keys, ensuring that duplicate requests do not create multiple reservations or bookings. All critical operations are wrapped in transactions to maintain atomicity.

In large-scale distributed systems, the booking workflow is often implemented using a Saga-style orchestration pattern, where failures in downstream services trigger compensating actions such as inventory release, payment rollback, or reservation expiration to maintain consistency across services.

The service is also event-driven, emitting events such as reservation creation, booking confirmation, and cancellation. These events enable asynchronous updates to Notification, Search, and Analytics systems without introducing tight coupling.

Pricing Service

The Pricing Service is responsible for calculating and serving hotel prices in real time while supporting dynamic pricing, discounts, promotions, taxes, and seasonal adjustments. Its primary goal is to ensure that users see accurate and competitive pricing while allowing hotels to optimize revenue based on demand and occupancy patterns.

Unlike Inventory or Booking services, the Pricing Service is primarily read-heavy and computation-driven. It must handle extremely high query volume during search while continuously ingesting pricing updates from hotel partners, promotional systems, and demand forecasting engines.

At its core, the service maintains base room prices, which are then adjusted dynamically using multiple pricing factors such as demand, occupancy, seasonality, events, geography, holidays, and booking lead time.

The service typically uses a combination of RDBMS for configuration consistency and Redis/cache layers for low-latency reads.
CREATE TABLE room_pricing (
    hotel_id BIGINT,
    room_type_id BIGINT,
    
    date DATE,
    
    base_price DECIMAL(10,2),
    currency VARCHAR(10),
    
    tax_percentage DECIMAL(5,2),
    discount_percentage DECIMAL(5,2),
    
    final_price DECIMAL(10,2),
    
    updated_at TIMESTAMP,
    
    PRIMARY KEY (hotel_id, room_type_id, date)
);
To support surge-like pricing and seasonal adjustments, the system may maintain a separate pricing_rules table.
CREATE TABLE pricing_rules (
    rule_id BIGINT PRIMARY KEY,
    
    hotel_id BIGINT,
    
    city VARCHAR(100),
    season VARCHAR(50),
    event_name VARCHAR(100),
    
    occupancy_threshold INT,
    price_multiplier DECIMAL(5,2),
    
    valid_from TIMESTAMP,
    valid_to TIMESTAMP
);
These rules allow the system to dynamically increase or decrease prices during holidays, festivals, weekends, or high-demand events.

Since pricing is requested extremely frequently during hotel searches, most responses are served from Redis or edge caches instead of recalculating prices for every request. Cache keys are often based on: hotel_id + room_type_id + date_range + occupancy

Short TTLs ensure that prices remain reasonably fresh while significantly reducing backend computation load.

Pricing information shown during search is often treated as a soft quote. Before final booking confirmation, the Booking Service performs a final price validation to ensure that no price changes occurred during the booking flow.

The Pricing Service is designed to scale horizontally because pricing reads massively outnumber writes. Frequently accessed pricing data is cached aggressively, while databases are partitioned by hotel_id or region to distribute load efficiently.

Additionally, CDN-backed APIs may be used for highly cacheable pricing metadata, reducing latency for geographically distributed users.

Review Service

At its core, the service stores ratings, textual reviews, media attachments, and review metadata submitted by users after completing their stay. To maintain authenticity and prevent abuse, reviews are typically allowed only for users with a verified booking history.

Unlike transactional systems such as Booking or Inventory, the Review Service is primarily read-heavy, with far more review fetches than review submissions. This makes it well-suited for scalable storage systems and aggressive caching strategies.

The service may use a combination of RDBMS for consistency and NoSQL systems for large-scale read optimization.
CREATE TABLE reviews (
    review_id BIGINT PRIMARY KEY,
    
    hotel_id BIGINT,
    user_id BIGINT,
    booking_id BIGINT,
    
    rating DECIMAL(2,1),
    
    title VARCHAR(255),
    review_text TEXT,
    
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);
The rating field is commonly aggregated into hotel-level metrics such as average rating and review count. To optimize reads, systems often maintain a separate aggregated ratings table.
CREATE TABLE hotel_rating_summary (
    hotel_id BIGINT PRIMARY KEY,
    
    average_rating DECIMAL(2,1),
    total_reviews BIGINT,
    
    updated_at TIMESTAMP
);
This avoids recalculating averages across millions of reviews during every search query.

Since review data is heavily consumed during hotel browsing, the service uses Redis caching and denormalized read models to reduce database load. Frequently accessed review summaries are cached aggressively, while detailed review pages may use pagination and lazy loading.

For large-scale deployments, review text may be stored in distributed NoSQL systems such as Cassandra or document databases to support horizontal scaling and high read throughput.

The Review Service directly impacts the Search Service. Aggregated ratings, review counts, sentiment analysis scores, and recency of reviews are often included in search indexes and ranking algorithms. For example, hotels with consistently high ratings and recent positive reviews may receive better ranking visibility compared to poorly rated properties.

Additionally, reviews may go through asynchronous moderation workflows before becoming publicly visible.

Notification Service

The Notification Service ensures that customers receive timely information such as booking confirmations, payment receipts, cancellation alerts, reminders, refund notifications, and promotional offers through multiple channels like email, SMS, and push notifications.

This service is typically designed as an asynchronous event-driven microservice. Instead of sending notifications directly during the booking flow, other services publish events such as BookingCreated, PaymentSuccessful, or BookingCancelled to a message broker like Kafka or RabbitMQ. The Notification Service consumes these events and processes them independently, ensuring that notification failures do not impact the core booking experience.

The service often integrates with external providers such as SendGrid, Amazon SES, Twilio, or Firebase Cloud Messaging for message delivery. Templates are commonly used to generate dynamic and personalized notifications, including booking details, payment amounts, travel dates, and user information.

System Guarantees

1. The system ensures low latency search by using specialized search engines such as Elasticsearch with denormalized hotel indexes optimized for full-text search, filtering, and geo queries. Frequently accessed search responses and pricing metadata are cached using Redis and CDNs, while pagination, sharding, and query optimization reduce response time even across millions of hotels. Additionally, search operates on precomputed indexes instead of transactional databases, avoiding expensive joins during runtime.

2. The system achieves high scalability through a microservices architecture, where services such as Search, Booking, Inventory, Pricing, and Reviews scale independently based on workload patterns. Read-heavy services use horizontally scalable datastores and caching layers, while asynchronous communication through event streams (Kafka/SQS-like systems) decouples producers and consumers. Data is also partitioned geographically or by hotel_id to distribute load efficiently across clusters.

3. The system maintains strong consistency for bookings by relying on transactional relational databases, atomic updates, and a two-phase reservation model. Before confirming a booking, inventory is temporarily reserved using soft locks, ensuring that rooms are held consistently across all requested dates. Critical operations such as reservation creation, inventory deduction, and booking confirmation are executed within transactions to guarantee all-or-nothing behavior.

4. High availability is achieved by deploying services across multiple nodes, regions, and availability zones. Load balancers distribute traffic across healthy instances, while replicated databases and distributed caches ensure redundancy. Stateless application servers allow rapid failover and autoscaling, and event-driven workflows help the system degrade gracefully during partial failures without completely blocking user operations.

5. The system prevents overbooking using a combination of row-level locking, atomic inventory updates, reservation TTLs, and transactional workflows. Inventory is reserved before payment begins, ensuring that concurrent users cannot purchase the same room simultaneously. If payment fails or times out, the reservation is automatically released. Additional safeguards such as idempotency keys and database constraints ensure correctness even under retries or extreme concurrency.

Conclusion

Designing a system like Booking.com is about balancing fast search with strong consistency in bookings. Unlike ride systems, where real-time matching dominates, hotel systems push complexity into search indexing and transactional integrity to ensure correctness and scalability.
Nagesh Chauhan
Nagesh Chauhan
Principal Engineer | Java Β· Spring Boot Β· Python Β· Microservices Β· AI/ML

Principal Engineer with 14+ years of experience in designing scalable systems using Java, Spring Boot, and Python. Specialized in microservices architecture, system design, and machine learning.

Share this Article

πŸ’¬ Comments

Join the discussion