Design Airbnb (Hotel Reservation System)

[!NOTE] This module explores the core principles of Design Airbnb (Hotel Reservation System), deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.

1. Problem Statement

Designing a reservation system like Airbnb or Expedia involves more than just a search engine. The core complexity lies in Inventory Management: ensuring that a room is never booked by two people at the same time (Double Booking), especially during high-demand events (like the Eras Tour or World Cup).

The Hard Part: Maintaining strict accuracy across distributed servers when thousands of users click “Book Now” simultaneously.


2. Requirements & Goals

Functional Requirements

  1. Search Properties: Find available listings based on location, dates, and guests.
  2. Book Property: Reserve a property for specific dates.
  3. Payment Integration: Securely process payments to confirm the booking.
  4. Manage Listings: Owners can update availability, photos, and prices.

Non-Functional Requirements

  1. Strong Consistency: We cannot allow double-bookings (crucial).
  2. Availability: Users must be able to search even if the booking service is slow.
  3. Low Latency: Search results must be fast.
  4. Idempotency: Clicking “Pay” twice shouldn’t charge the user twice.

3. High-Level Architecture

The system distinguishes between the Search Path (read-heavy, eventual consistency OK) and the Booking Path (write-heavy, strong consistency REQUIRED).

Search Service
(Elasticsearch)
Booking Service
(Postgres / ACID)
Handles Locking
Payment Gateway
(Stripe / Braintree)
Inventory DB
Master (Writes) ---- Read Replicas

4. Deep Dive: Zero-Double-Booking Strategy

How do we prevent two users from booking the same room? We use Database Locking.

Option A: Pessimistic Locking

The database locks the row the moment a user starts the checkout.

BEGIN TRANSACTION;
SELECT room_count FROM rooms WHERE id = 123 FOR UPDATE; -- LOCKS THE ROW
UPDATE rooms SET room_count = room_count - 1 WHERE id = 123;
COMMIT;
  • Pros: Guaranteed accuracy.
  • Cons: If a user sits on the payment page for 5 minutes, others are blocked. Low throughput.

Add a version column. We only update if the version hasn’t changed.

UPDATE rooms SET
  room_count = room_count - 1,
  version = version + 1
WHERE id = 123 AND version = current_version;
  • Pros: High throughput; no row-level locking until the actual commit.
  • Cons: If 100 people try at once, 99 will get a “Conflict” error and have to retry.

[!TIP] Analogy: The Last Slice of Pizza Pessimistic: You put your hand on the pizza slice while deciding if you want it (no one else can take it). Optimistic: Everyone tries to grab the slice at once. The first hand that touches it wins; everyone else misses.


5. Handling Payments and Inventory

A common bug: The payment succeeds, but the inventory update fails. Now you have money but no room.

Solution: The Reservation Pattern.

  1. Reserve: Mark the room as “Pending” in the DB.
  2. Payment: Call the Payment Service (Stripe).
  3. Confirm: Only if payment is successful, update the status to “Booked.”
  4. Timeout: If payment isn’t received in 10 minutes, a background worker releases the “Pending” lock.

Search requires complex filtering (Price < $200, Has WiFi, Near Beach).

  1. Read-Replicas: Use multiple read-only copies of the main DB for basic lookups.
  2. Elasticsearch: Sync property metadata to Elasticsearch for rich, high-performance search queries.
  3. Geospatial Indexing: Use Google S2 or H3 to quickly find properties within a specific map coordinate.

7. Interview Gauntlet

  1. How do you handle high-traffic dates (New Year’s Eve)?
    • Ans: Use a Distributed Lock (Redis) or a Virtual Queue to limit the number of users entering the checkout flow simultaneously.
  2. What if the property owner updates the price during a user’s checkout?
    • Ans: Capture the price at the moment the “Reserve” call is made. The user pays that price. This is a common business rule (Price Guarantee).
  3. How do you ensure Idempotency for payments?
    • Ans: Every booking has a unique booking_id. Pass this ID as an Idempotency-Key to Stripe. If the request is retried, Stripe returns the original success instead of charging again.

8. Summary

  • Consistency: Use the RDBMS (Postgres) and Optimistic Locking for inventory safety.
  • Payments: Use the “Reserve → Pay → Confirm” state machine.
  • Search: Use Elasticsearch for flexible, low-latency filtering.
  • Recovery: Use background workers to clean up expired or failed reservations.