Courses › System Design › Question Breakdowns

Design Airbnb

Per-night availability, atomic date-range booking with holds, availability-aware geo search and index freshness.

Design a home-rental marketplace like Airbnb: hosts list places with calendars and prices, guests search by location and dates, and book a stay that must never overlap another booking. It's a question about search over location × availability, date-range booking without double-booking, and a multi-step reservation that spans payment and host approval.

Requirements

Functional

  • Hosts can list a property (location, photos, amenities, nightly price) and manage its availability calendar
  • Guests can search by location, dates and guest count, with filters (price, type, amenities)
  • Guests can view a listing and book it for a date range; the booking is paid and confirmed
  • Guests and hosts can see and cancel reservations (by policy)

Out of scope: reviews, messaging between guest and host, dynamic pricing models, experiences.

Non-functional

  • No double-booking: two guests can never hold overlapping nights on the same listing
  • Fast search: results in < 500ms; search is ~100× more frequent than booking
  • Scale: ~8M active listings, ~100M searches/day (~5k/s peak), ~1M bookings/day (~30/s peak, more at holidays)
  • Highly available search; booking prefers consistency over availability

[!TIP] Availability is a date-range problem Unlike Ticketmaster (one seat, one event), a listing is booked by ranges of nights, and search must exclude listings unavailable for *any* night in the requested range. Model availability per listing per night and most of the design follows (see Booking Time-Slot Inventory).

Capacity estimates

Core Entities

  • Listing — host, location (lat/lng), attributes, base price, photos
  • Calendar night — listing, date, status (available / blocked / booked), price
  • Reservation — listing, guest, check-in, check-out, total, status (held / confirmed / cancelled)
  • Payment — reservation, amount, status

API

GET  /v1/search?lat=40.71&lng=-74.0&radiusKm=5&checkin=2026-12-20&checkout=2026-12-24&guests=2&maxPrice=250
→ { "listings": [{ "id": "l_1", "pricePerNight": 189, "total": 756, ... }], "cursor": "..." }

POST /v1/reservations   Idempotency-Key: ...
{ "listingId": "l_1", "checkin": "2026-12-20", "checkout": "2026-12-24", "guests": 2 }
→ 201 { "reservationId": "r_9", "status": "held", "expiresAt": "...", "paymentIntent": "..." }

PUT  /v1/listings/{id}/calendar   { "dates": ["2026-12-31"], "status": "blocked" }

High-Level Design

guest = client "Guest App" @0,1
search = service "Search Service" @1,0
idx = store "Search Index\n(geo + availability)" @2,0
book = service "Booking Service" @1,1
cal = store "Listings & Calendar DB\n(Postgres, by listing)" @2,1
pay = service "Payment Service" @3,1
cdc = queue "Change Stream (CDC)" @3,0
host = client "Host App" @0,2
lst = service "Listing Service" @1,2
guest -> search
search -> idx
guest -> book : reserve
book -> cal : hold nights
book -> pay
host -> lst
lst -> cal
cal --> cdc
cdc --> idx : update
  • Search queries an index (Elasticsearch) by geo + filters + availability, then ranks results.
  • Booking places a short hold on the requested nights in the calendar DB (transaction), takes payment, and confirms.
  • Hosts edit listings and calendars in the same DB; a CDC stream keeps the search index in sync within seconds.

Deep Dives

1. Booking without double-booking

Store one row per (listing, night). Booking 4 nights locks and updates those 4 rows in one transaction:

BEGIN;
SELECT date FROM calendar
WHERE listing_id = :l AND date >= :checkin AND date < :checkout AND status = 'available'
FOR UPDATE;
-- proceed only if all nights came back
UPDATE calendar SET status = 'held', reservation_id = :r, held_until = now() + interval '15 minutes'
WHERE listing_id = :l AND date >= :checkin AND date < :checkout;
COMMIT;

Alternatives: a Postgres exclusion constraint on daterange(checkin, checkout) per listing (the database rejects overlapping reservations itself), or optimistic concurrency with a version per listing. Contention is low — conflicts happen only on the same listing — so row locks are cheap. Shard by listing ID so a booking never spans shards.

2. Holds, payment and expiry

held -> confirmed -> completed
held -> expired
confirmed -> cancelled

The hold reserves the nights for ~15 minutes while the guest pays. Payment success (via an idempotent payment intent — see Design a Payment System) confirms the reservation; a sweeper releases expired holds. Listings with host approval ("request to book") keep the hold for up to 24h, authorizing the card but capturing only on approval.

3. Search: location × dates × filters

  • Geo: index listings with geo_point and filter by radius or the map's bounding box (see Design Yelp (Nearby Places)).
  • Availability: the hard filter. Options:

Indexing availability in the search documents is the usual answer. Because the index can be slightly stale, the booking step always re-checks the calendar — search is a hint, the calendar is the truth.

4. Ranking

Distance alone is a poor ranking. Score candidates by price fit, quality (ratings, host response rate), conversion likelihood and freshness — a learned model applied to the top few hundred geo/availability matches (two-stage retrieve → rank).

5. Read scaling and caching

  • Listing details and photos are read-heavy and change rarely: cache in Redis, photos via a CDN with resized variants.
  • Search results for popular areas and dates can be cached briefly (seconds) keyed by a normalized query.
  • The calendar DB serves bookings and host edits only; search never reads it directly.

6. Holiday spikes

New Year's Eve in a hot city creates many guests racing for few listings. Contention is still per listing, so row locks hold up; protect the booking service with rate limits and fail fast with "just booked" when a hold can't be taken, then re-run search.

What's expected at each level

  • Mid-level: listings, a calendar, search by location and dates, a transactional booking.
  • Senior: per-night rows (or range constraints) for no double-booking, holds with expiry tied to payment, availability in the search index with re-check at booking.
  • Staff: sharding by listing, request-to-book flows with authorization vs. capture, ranking as a two-stage system, index freshness trade-offs and holiday spikes.