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 -> cancelledThe 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_pointand 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.