~learn.

Airbnb Stays Marketplace, the whole design.

A marketplace of 9 million+ one-of-a-kind homes, by searching a fast in-memory copy of every listing's place, free nights and prices, deciding exactly only at Reserve, and moving guests' money through a double-entry ledger that holds it until check-in.

7 diagrams · 8 sections · revise in ~10 min · Download PDF prints it in the light theme, diagrams included

SQ-24 · Airbnb Stays Marketplace · design sheethimanshukukreja.in/learn/sheets/bonus-24-airbnb-stays-marketplace

At a glance#

Number Value What it drives
Listings and hosts 9M+ listings · 5.5M+ hosts (May 2026) most homes are one unit: a night is free or taken
Nights booked 533M in 2025 · ~1.46M a day · $91.3B ~365,000 reservations a day at 4 nights a stay (assumed), ~13 / s at peak
Search ~14,000 requests / s at peak (assumed) search, not booking, sizes the read path
Listing updates ~10,000 / s, mostly prices and availability the index is updated by events, searchable within ~1 minute
Availability for every listing 365 bits each · ~0.4 GB filter by dates in memory; prices ~13 GB
Imported calendars every 3 hours · ~1.5 h blind on average ~4M feeds is only ~370 fetches / s; staleness, not load, is the risk
Money held $6.9B at end 2025 · ~28 days of bookings a ledger, not a payments table, is the system of record
Payout business day after check-in · ~50 currencies a payout scheduler with a holiday calendar per country

Architecture#

DiagramSearch reads a copy; calendars, prices and money live elsewhere
%% title: Search reads a copy; calendars, prices and money live elsewhere
flowchart TD
  G@{ icon: "sd:phone", label: "Guest app<br/>map rectangle · dates · guests" } ==> API@{ icon: "sd:api", label: "API gateway" }
  API ==> ROOT["Search root<br/>fan out · merge · re-rank · pins"]:::hot
  ROOT ==> LEAF["Search leaves<br/>geo cells · night bits · prices · ranker"]:::core
  EXT@{ icon: "sd:globe", label: "Other travel sites<br/>.ics feeds" } -.-> ICAL@{ icon: "sd:cron", label: "iCal poller<br/>by risk · refresh at Reserve" }
  ICAL --> CAL["Calendar service<br/>nights · blocks by source · rules"]:::service
  PR@{ icon: "tech:spark", label: "Smart Pricing<br/>Spark batch + fast path" } --> CAL
  CAL --> BUS@{ icon: "tech:kafka", label: "Kafka<br/>~10,000 listing updates / s" }
  BUS ==> LEAF
  API --> RES["Reservations<br/>unique listing + night"]:::service
  RES --> RISK@{ icon: "sd:shield", label: "Risk scoring<br/>allow · friction · block" }
  RES --> PAY["Payments<br/>pay-in · ledger · payouts"]:::core
  class G client
  class API edge
  class EXT external
  class BUS queue
  class ICAL,PR,RISK service
DiagramThe ranking funnel, each stage an order of magnitude smaller
%% title: The ranking funnel, each stage an order of magnitude smaller
flowchart TD
  F["Free for the dates and party<br/>~20,000 homes in a big search"]:::service
  EBR["Retrieval · two towers, IVF clusters<br/>keep ~1,000"]:::core
  DNN["First-stage ranker<br/>booking probability"]:::core
  SET["Setwise re-ranker at the root<br/>the page as a whole"]:::hot
  PINS["Map pins<br/>full pins · mini-pins"]:::ok
  EMB@{ icon: "sd:ml", label: "Listing vectors<br/>rebuilt daily offline" }
  F ==> EBR ==> DNN ==> SET ==> PINS
  EMB -.-> EBR
  class EMB store

Key flows#

DiagramRohan drags the map, then reserves Meera's home
%% title: Rohan drags the map, then reserves Meera's home
sequenceDiagram
  autonumber
  participant R as Rohan's app
  participant S as Search root
  participant L as Leaves
  participant B as Reservations
  participant K as Risk scoring
  participant P as Payments
  R->>S: Map rectangle, 6–9 Nov, 5 guests
  S->>L: Fan out to every leaf
  L-->>S: Free all 3 nights, total price, ranked
  S-->>R: 18 cards, full pins and mini-pins
  R->>B: Reserve with the signed quote
  B->>K: Score this booking
  K-->>B: Low risk, no friction
  B->>B: Insert 3 nights, unique per listing-night
  B->>P: Charge ₹38,400, hold until check-in
  P-->>R: Confirmed
DiagramRefresh on intent: the Reserve button checks the other sites
%% title: Refresh on intent: the Reserve button checks the other sites
sequenceDiagram
  autonumber
  participant R as Rohan's app
  participant B as Reservations
  participant Q as iCal poller
  participant O as Other site
  participant C as Calendar service
  R->>B: Reserve 6–9 Nov
  B->>Q: Feeds older than a few minutes?
  Q->>O: Conditional GET, about 1 s timeout
  alt 304 not modified
    O-->>Q: 304
    Q-->>B: Nothing new
    B->>C: Insert 3 nights
  else New file blocks 7 Nov
    O-->>Q: 200 with the new busy dates
    Q->>C: Apply snapshot for this source
    C-->>R: These dates are no longer available
  end
  Note over Q,O: no answer in time · book and flag the stay for the host
DiagramThe money, from Rohan's charge to Meera's payout
%% title: The money, from Rohan's charge to Meera's payout
sequenceDiagram
  autonumber
  participant P as Pay-in
  participant L as Ledger
  participant S as Payout scheduler
  participant O as Payout
  participant M as Meera's bank
  P->>L: Charge posted · held for host, unearned fee, tax
  Note over L: every posting sums to zero, append-only
  S->>S: Check-in Friday 6 Nov · next business day Monday
  S->>O: Pay stay, key payout per stay
  O->>L: Intent recorded before the call
  O->>M: Send rupees with the same key
  M-->>O: Accepted
  O->>L: Payable out, payouts in transit

Data model#

DiagramCore entities
%% title: Core entities
classDiagram
  class Listing {
    +listingId : int
    +geoCell : cell id
    +freeNights : 365 bits
    +nightlyPrice : 365 values
    +minNights : int
    +checkinDays : weekdays
  }
  class StayNight {
    +listingId : int
    +night : date · unique with listing
    +stayId : string
  }
  class CalendarBlock {
    +listingId : int
    +night : date
    +source : feed id or host
    +uid : iCal event id
  }
  class Quote {
    +listingId : int
    +nights : date range
    +total : money
    +expires : minutes
  }
  class Posting {
    +key : idempotency key
    +currency : string
    +entries : balanced, sum zero
  }
  class Payout {
    +key : payout per stay
    +host : hostId
    +status : INTENT / SENT / FAILED
  }
  Listing "1" --> "many" StayNight : booked nights
  Listing "1" --> "many" CalendarBlock : imported or manual
  Quote "1" --> "0..1" StayNight : becomes
  StayNight "many" --> "many" Posting : money events
  Posting "many" --> "1" Payout : paid by

Lifecycle#

DiagramA stay, from Reserve to payout
%% title: A stay, from Reserve to payout
stateDiagram-v2
  [*] --> QUOTED: search returns a signed quote
  QUOTED --> VERIFYING: risk score in the middle
  QUOTED --> PAYMENT_PENDING: low risk
  VERIFYING --> PAYMENT_PENDING: friction cleared
  VERIFYING --> REJECTED: friction failed or blocked
  PAYMENT_PENDING --> CONFIRMED: charge or webhook succeeds
  CONFIRMED --> CHECKED_IN: check-in day · fee becomes revenue
  CHECKED_IN --> PAID_OUT: business day after check-in
  CONFIRMED --> CANCELLED: refund by reversing entries
  PAID_OUT --> [*]
  REJECTED --> [*]
  CANCELLED --> [*]
  class PAID_OUT ok
  class REJECTED bad
  class VERIFYING queue
  class PAYMENT_PENDING queue

Decisions#

Decision Chose Over Because
Availability in search Night bits and prices in leaf memory, updated by events Calling the calendar service per candidate thousands of candidates per search at ~14,000 searches / s
Leaf partitioning By listing id, fan out to all leaves By region a festival in Goa would melt a regional shard
Retrieval index Two-tower vectors, IVF clusters as a filter field HNSW graph HNSW memory grew under ~10,000 updates / s and did poorly with filters
Map pins Fewer, likelier pins · mini-pins on desktop All 18 as full pins 95% of searchers click ≤ 12 pins; mini-pins get ~8× fewer clicks
Own double bookings Unique key on (listing, night) Counts and locks most homes are one unit per night
iCal staleness Blocks tagged by source, polling by risk, refresh at Reserve A fixed 3-hour round-robin ~1.5 h average blind window; the Reserve press is when it bites
Price changes Host range, ±15% a step, open quotes untouched Applying the model's price directly hosts must trust it; guests pay the quote they saw
Money Append-only double-entry ledger, payout key before the call Status columns on a payments table billions held for weeks; retries must never pay twice

When it breaks#

Failure What users see Contain it with
Another site books a night between polls two guests for one house refresh on Reserve with a short timeout; poll risky feeds every few minutes
A partner serves an empty or shifted .ics months of taken nights shown free keep the last good snapshot; confirm big drops twice; alert the host
Price rises during checkout a surprise total signed quote with an expiry; pricing skips open quotes
Index lag grows to 15 minutes stale prices, more "just booked" alert on event-to-searchable; reserve-time check stays exact
Payout day is a bank holiday or the account is closed late or returned payout holiday calendar per country; reversing entries back to payable
Fraud ring on New Year's Eve chargebacks weeks later targeted friction for last-minute new accounts on hot nights
Risk model times out slow or failed checkout conservative fallback rules inside the latency budget

Say it in the interview#