Skip to content
System design course
Ch.4 · Designing real systems·how to build it ·7 min read

Building Airbnb

Implement availability search, the hold-then-confirm booking with a database exclusion constraint, and async index propagation.


Combine geo candidates with a date-range availability filter and other filters:

def search(area, check_in, check_out, filters):
    candidates = geo_index.in_area(area)                  # geohash/quadtree (Yelp)
    return rank([
        l for l in availability_index.batch(candidates)
        if l.available(check_in, check_out)               # no overlap with booked ranges
        and passes(l, filters)
    ])

The availability index is denormalized for fast filtering (eventually consistent with the source-of-truth calendar) — so re-check at booking time.

The booking guarantee: an exclusion constraint

The bulletproof defense against double-booking is a database constraint that physically forbids overlapping bookings — so even with a race, the second insert fails:

-- one booking per listing per night (Postgres exclusion constraint over a date range)
CREATE TABLE bookings (
  listing_id  bigint,
  during      daterange,
  guest_id    bigint,
  EXCLUDE USING gist (listing_id WITH =, during WITH &&)   -- && = ranges overlap → reject
);

No matter how the application races, the DB lets at most one booking exist for any overlapping range. This is the safety net under any application-level checks.

Hold → pay → confirm

A short hold reserves the dates during checkout; payment then confirms; failure releases:

def start_booking(listing_id, check_in, check_out, guest):
    if not lease.acquire(f"hold:{listing_id}:{check_in}:{check_out}", ttl="10m"):
        raise DatesUnavailable()                          # someone else is checking out
    return checkout_session(listing_id, check_in, check_out, guest)

def confirm_booking(session):
    try:
        payment.charge(session)                           # take payment first
        with txn():
            insert_booking(session)                       # exclusion constraint enforces no overlap
    except OverlapViolation:
        payment.refund(session); raise DatesUnavailable() # lost the race → refund
    finally:
        lease.release(hold_key(session))

The lease gives good UX (dates held during checkout) and the constraint gives correctness (the real guarantee) — belt and suspenders.

Availability modeling

Store bookings as date ranges (compact) and check overlap with &&; the search index materializes per-listing availability (or booked ranges) for fast filtering. The DB calendar is the source of truth; the index trails it.

Async index propagation

When a booking or host calendar change lands, propagate to the search index asynchronously:

def on_booking_committed(booking):
    index_queue.publish({"listing": booking.listing_id, "change": "booked",
                         "range": booking.during})
# indexer updates the availability index (eventually consistent)

A just-booked listing might appear in search for a moment — caught by the re-check at booking + the exclusion constraint.

Scale and failure handling

  • Search (read-heavy) → geo + availability indexes, cached, replicated.
  • Booking (consistency-critical) → transactional store, sharded by listing; the exclusion constraint is the ultimate guard.
  • Payment fails / user abandons → hold expires (lease TTL), dates freed.
  • Two simultaneous bookings → one wins the constraint, the other gets a clean “unavailable” + refund.
  • Index lag → re-check availability at booking; never trust the index for the final decision.
  • Hot listing (a popular property on a holiday) → contention on its calendar rows; the constraint serializes correctly; holds reduce wasted payment attempts.

The takeaway

Concrete signals: geo + date-range availability search (eventually consistent), a hold (lease) → pay → confirm flow, and a database exclusion/unique constraint as the unbreakable no-double-booking guarantee, with async index propagation. Splitting fast eventual search from a strongly-consistent booking transaction is the reusable pattern for any inventory/reservation system — which is exactly Ticketmaster next.