The whole design, in writing
Learn system design by building a lodging marketplace like Airbnb step by step. An interactive guide covering listings, geo search with filters, availability calendars, booking without double-booking, the payment hold-and-capture flow, reviews and reindexing, and scaling reads.
Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.
The big idea
What is Airbnb?
A two-sided marketplace: hosts list homes, guests search by place and dates and book a stay. The reads (search) are enormous and demand geo + availability filtering; the writes (bookings) are rare but absolutely must never double-book a home.
Build a fast, filterable search over a spatial index and availability calendars, then a transactional booking path that reserves dates atomically and holds payment. Search optimizes for scale; booking optimizes for correctness.
What the new pieces do
- Guestclient
- Searches by place and dates, browses listings, and books a stay. Search is the heavy, read-mostly path; booking is rare but must be rock-solid.
Step 1 · The skeleton
List and find homes
Hosts need to publish listings and guests need to find them. Even ignoring geography and dates, you need a clean split between the catalog of homes and the act of searching it.
Guest searches massively outnumber host writes. How do you structure search vs the catalog?
Heavy search load hammering the source-of-truth DB threatens the writes (new listings, bookings) and can’t scale independently. Search should not contend with the system of record.
The gateway routes host writes to the Listings DB and guest queries to a search service reading an index derived from it. Search scales for read traffic while writes stay simple — and a search overload never threatens the source of truth.
You can’t pre-cache the combinatorial explosion of place × dates × filters, and listings/availability change constantly. You need a live searchable index, with caching layered on later for hot queries.
An API Gateway sends host writes to a Listings DB and guest queries to a Search Service that reads from it. This separation lets search scale for heavy read traffic while writes stay simple and consistent.
What the new pieces do
- Hostclient
- Publishes a listing with photos, price and an availability calendar. Writes are infrequent compared to the flood of guest searches.
- API Gatewaybackend
- The single entry point routing searches to the search service and bookings to the booking service, behind a load balancer.
- Search Serviceservice
- Finds listings matching a location, date range, price and amenities, ranked for relevance. The most-hit service in the whole system.
- Listings DBstore
- The source of truth for listings: descriptions, photos (in blob storage), pricing rules and host info. Read constantly during search.
Step 2 · Search by place
Geo indexing
“Homes near this map area” is the core query, but computing distance to every listing on earth per search is impossibly slow. A normal database index on lat/long doesn’t handle 2D proximity well.
"Homes near this map area" is the core query. How do you avoid computing distance to every listing on earth?
A 1-D index can’t answer 2-D proximity — nearby in one axis can be far in the other, so you’d still scan and distance-check huge ranges. 2-D search needs a spatial structure.
Distance from one origin doesn’t help search from arbitrary map locations and must be recomputed per query. You need a structure indexed by region, not by distance to one point.
Geohashing turns 2-D coordinates into a sortable prefix; quadtrees split dense areas. Proximity search becomes a bounded lookup of listings in the viewport, then filter/rank that small set.
Build a spatial index — a quadtree or geohash — that buckets listings by region so you can fetch just the ones inside the viewport quickly, then filter and rank that small set.
- quadtreespatial split
- viewportbounded query
- msgeo lookup
What the new pieces do
- Geo Indexindex
- A spatial index that answers “listings within this map area” quickly, instead of scanning every home on earth and computing distances.
Back of the envelope
- geohash = sortable prefix
- 2-D coords → a 1-D bounded range
- quadtree splits dense areas
- a viewport query, not a planet scan
- fetch in-viewport, then filter/rank
- work on a small candidate set
Step 3 · Only what’s free
Availability calendars
Guests search by date range, so results must exclude homes already booked for those nights. Availability changes constantly as bookings happen, and it gates both search and booking.
Guests search by date range, so results must exclude homes booked for those nights. How do you model "is it free?"
A home is free some nights and taken others — a single flag can’t express a calendar. A stay spans a contiguous range of nights that must all be free.
One authoritative availability calendar per listing lets search filter by the requested range and booking later reserve against the same calendar. Modeling a date range makes both filtering and the no-double-book check natural.
Scanning and aggregating all bookings per listing on every search is far too slow on the hottest path. Maintain availability as its own authoritative, queryable calendar updated as bookings happen.
Keep a per-listing Availability calendar of open/taken nights. Search filters candidates by the requested range; booking will later reserve against the same calendar. One authoritative place for “is this home free?”.
What the new pieces do
- Availabilitystore
- Per-listing calendar of which nights are open or taken. Both search (filter by dates) and booking (reserve dates) depend on it being correct.
Step 4 · Never double-book
Transactional booking
Two guests try to book the same home for overlapping dates at the same instant. Without care, both succeed — and one shows up to a taken house. This is the marketplace’s cardinal sin.
Two guests try to book the same home for overlapping dates at the same instant. How do you guarantee only one wins?
A gap between check and write is the classic race — both guests see "free", both write, both think they booked. Check-then-set must be one indivisible step.
Confirming two overlapping stays means someone arrives at a taken house — the marketplace’s cardinal sin. The conflict must be prevented at write time, not reconciled after.
Check the range is free and mark it taken together, guarded by a unique (listing, night) constraint or row locks. Concurrent conflicting bookings collapse to exactly one winner; the other cleanly retries.
The Booking Service reserves the requested nights in a single atomic transaction: check the range is free and mark it taken together, guarded by a constraint or lock on the listing+dates. Concurrent conflicting bookings — exactly one wins.
- atomicreserve
- 1winner per night
- 0double-bookings
What the new pieces do
- Booking Serviceservice
- Turns “I want these dates” into a confirmed reservation — atomically, so two guests can’t book the same home for overlapping nights.
Back of the envelope
- check + mark taken = 1 transaction
- no gap for a race
- unique (listing, night)
- the DB enforces one winner
- loser retries cleanly
- 0 double-bookings
Step 5 · Take the money
Payment hold & capture
Confirming a stay involves money across two parties, and payment can fail or be retried. Charging twice, or reserving dates without securing payment, both break trust.
Confirming a stay involves money and the payment can fail or be retried. How do you take it safely?
Reserving without securing funds means confirmed dates with no guaranteed payment, and no clean way to release them on failure. Tie the hold to the booking and release both if payment fails.
Hold the card to secure funds while finalizing, capture on confirmation, and release the hold and the dates if it ultimately fails. An idempotency key makes the inherently retried payment path safe from double-charges.
Charging before a booking is even confirmed means refunds, double-charges on retries, and money taken for stays that never happen. Separate auth from capture.
On booking, authorize (hold) the guest’s card; on confirmation, capture it and schedule the host payout. Use an idempotency key so a retried request never double-charges, and release the hold (and the dates) if payment ultimately fails.
What the new pieces do
- Paymentsservice
- Authorizes the guest’s card to hold funds at booking and captures on confirmation, with idempotency so a retry never double-charges.
Step 6 · Keep it fresh
Reviews & reindexing
New listings, price changes and reviews must show up in search, and ranking should reflect quality — but doing that work synchronously on the booking/listing path would slow everything down.
New listings, price changes and reviews must reach search, and ranking should reflect quality. How, without slowing writes?
Doing reindexing and review aggregation inline makes every write wait on search-index updates, coupling a fast critical path to slow propagation. These effects are eventually-consistent by nature.
Querying the source-of-truth DB per search reintroduces the read load on writes you separated in step 1, and can’t do fast ranking. Search needs its own index, kept fresh out-of-band.
Listing/booking changes flow to an event stream; consumers reindex search, roll reviews into ratings, update payouts and notify — off the request path. Writes stay fast, search stays current (eventually).
Emit listing and booking changes as events. Consumers reindex the search service, aggregate reviews into ratings, update host payouts and send notifications — all asynchronously, so the write path stays fast and search stays current.
What the new pieces do
- Eventsbus
- A stream of listing and booking changes that drives search reindexing, review aggregation, host payouts and notifications asynchronously.
Back of the envelope
- change ⇒ event
- the write path doesn’t wait
- consumers: reindex, reviews, payouts
- eventually consistent by nature
- record change ≠ propagate effects
- decoupled via the stream
Step 7 · Scale the reads
The sharp edges
Search traffic dwarfs everything and spikes seasonally and by destination; popular cities create hotspots. One listings database can’t serve it all, and recomputing identical searches is wasteful.
Add read replicas for the listings data, cache popular search results and listing pages, and shard/partition by region. Pricing and ranking can be precomputed; the rare booking path stays transactional while reads scale out horizontally.
You did it
You just designed Airbnb.
Everything you assembled, in order
- A gateway splitting heavy search reads from rare, careful booking writes.
- A spatial index (quadtree/geohash) makes “homes near here” a bounded query.
- Per-listing availability calendars power both date filtering and booking.
- Atomic, lock-guarded booking so overlapping stays can never double-book.
- Payment hold-then-capture with idempotency keys for safe, retryable charges.
- Event-driven reindexing and review aggregation keep search fresh asynchronously.
- Read replicas, search caching and regional sharding scale the read-heavy load.