System Design Library

Airbnb / Booking

Search listings by location/dates and book without double-booking.

Open the interactive version → diagrams, practice & more

Requirements

Functional

  • Geo + availability search
  • Booking with no overlap
  • Pricing/calendar
  • Reviews

Non-functional

  • Consistent availability
  • Fast search

Scale

Millions of listings

The approach

Geo+attribute search index for discovery (eventual OK); bookings go through a transactional availability check (no overlapping reservations) with holds; calendar per listing.

Key components

Search index (geo) · listing DB · transactional booking service

Numbers that matter

Senior deep-dive

Search and booking are architecturally separate consistency domains — search is eventually consistent (showing a listing that was just booked is tolerable); the booking transaction must be strictly serializable (double-booking is catastrophic and unrecoverable).

Calendar availability is a specialized interval problem — naive row-per-date doesn't scale; you need an interval-based availability model (blocked ranges, not individual dates) with a smart overlap query.

Hold-then-confirm is the transactional primitive: a time-boxed lock (reservation hold, say 15 minutes) lets the guest fill payment details without holding a DB lock indefinitely — the hold expires atomically if payment fails.

Availability model: intervals, not daily rows

A naive model stores one row per available date: 365 rows per year per listing. Checking 500 listings for a 7-day stay in August means 3,500 row lookups. The interval model stores blocked ranges (`(listing_id, start_date, end_date, reason)`) and checks availability by querying for overlapping intervals: `WHERE listing_id = X AND NOT (end_date <= :checkin OR start_date >= :checkout)`. This is a much smaller table (bookings per listing, not days per listing) and a simple two-condition overlap check that indexes cleanly on `(listing_id, start_date)`.

The hold pattern: time-boxed reservation before payment

You cannot hold a DB lock for the 5-10 minutes a user spends entering payment details. The pattern: at 'Book Now' click, create a hold record in the DB (`status=held, expires_at=now+15m`) — this is a lightweight row that blocks other bookings from the same dates. A background job sweeps expired holds and releases them. On payment success, atomically update `status=confirmed`. On payment failure or timeout, the sweep releases the hold. The critical invariant: two holds for the same dates must be mutually exclusive — enforced by a unique constraint or serializable transaction at hold creation time.

Search consistency: eventual is fine, booking is not

When a listing is booked, it takes seconds to minutes for the availability index (Elasticsearch) to reflect the change. A guest might see the listing as available in search results, click through, and find it unavailable. This is expected behavior — Airbnb shows 'this listing is no longer available for your dates' at the booking attempt. The search index is optimized for speed and recall, not consistency. Eventual consistency in search is a conscious product decision: the alternative (synchronous index updates on every booking) adds latency to the hot-path transaction.

Geo search: what the listing index must support

Airbnb's discovery query is: 'listings in this bounding box, available for these dates, with these amenities, sorted by relevance.' Geo query + date filter + attribute filter must compose efficiently. Elasticsearch handles the geo + attribute part; date availability is an additional filter that the search layer applies against the availability service (or a pre-indexed availability bitmap). The challenge: pushing date availability into the search index requires rebuilding/updating index fields on every booking, which is expensive. The pragmatic split: geo+attributes in Elasticsearch, availability cross-checked from the availability service post-retrieval.

Pricing: not a static field

Listing prices are dynamic: base price + seasonality + demand-based surge + host-set special pricing for specific dates. Storing a single price field per listing is wrong. Airbnb's pricing is a per-night pricing calendar that the host manages, with Airbnb's Smart Pricing model overlaying demand signals. Exposing the right nightly total for a date range requires summing the pricing calendar for those nights — this must be fast enough to display inline in search results for 50+ listings. Pre-aggregated price ranges (min/max for next 30 days) are indexed for quick filtering; exact pricing is computed on the listing detail page.

What breaks at scale

Thundering herd on popular listings during holiday release windows: when a host opens their summer calendar, thousands of guests may attempt to book simultaneously, creating a write contention storm on the `reservations` table. Use serializable isolation and accept that some requests will retry — but at high contention this degrades to a queue. Calendar sync with external systems (hosts who manage their listing on VRBO too): stale external calendars cause Airbnb to show availability that's already been booked elsewhere — Airbnb must poll external iCal feeds frequently and apply pessimistic blocking on external-calendar uncertainty. Refund and cancellation state machine complexity: a cancelled booking must atomically release the calendar hold, initiate a refund, notify both parties, and update availability — any partial failure leaves the system in an inconsistent state.

In production

Airbnb's engineering blog describes their Chronos availability service and a reservation service backed by MySQL with pessimistic locking for the critical booking transaction. Search uses Elasticsearch with geo queries for listing discovery (eventual consistency is fine here). The real engineering challenge is the two-phase booking flow: search shows available listings (read from a cached availability index), but by the time the guest confirms, another guest may have booked. The fix is a re-check at commit time using a database-level serializable transaction with an explicit `SELECT ... FOR UPDATE` on the calendar rows — then either commit the booking or return a conflict error and let the frontend handle it gracefully.

Common mistakes

Related System Design Library

Part of System Design Library on SystemLore — system design interview prep with 148 deep topics, interactive diagrams, and a practice game. Practice this one →