~learn.

Side quest 24Stays marketplace

Airbnb Stays Marketplace.

Nine Million Homes, a Map That Only Shows What's Free, and Money Held Until the Guest Walks In

HARD49 MIN READ6 PHASES · 45 MIN INTERVIEW+120 XP

Mission brief

Design the marketplace behind a global home-stays platform

Quest log0/7 stages · 0/8 checkpoints · 0/7 quick checks
active listings from 5.5 million+ hosts in 220+ countries and regions (Airbnb, May 2026)
9 million+
nights and seats booked in 2025, worth $91.3 billion in bookings (Airbnb 10-K, 2025)
533 million
listing updates a second, mostly prices and availability, that search must see (Airbnb, 2026)
~10,000
currencies; the guest's money is held until check-in and paid to the host the business day after (Airbnb 10-K, 2025)
~50

Company names and logos are trademarks of their owners, shown only to identify each company. This course isn't affiliated with or endorsed by any of them.

watch it first · newThe whole design in a 14-minute animated lecture4 quiz checkpoints · ask the AI about any moment · captions, chapters and speed
Optional · timed run · 6 phases · 45 min

Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.

Intro · 4 min

How Do You Show a Family Only the Homes That Are Free, and Pay a Host You've Never Met?

It is a Tuesday night in Bengaluru, three weeks before the Diwali long weekend. Rohan Iyer has promised his parents, his wife and their two children three nights in Goa. He opens the app, types "North Goa", picks Friday to Monday, five guests, and drags the map over Assagao. Every pin he sees must be a home that is actually free for those three nights, at a price for those three nights, and the dozen pins on his screen have to be the dozen he is most likely to book out of the thousands in that rectangle.

Two hundred metres from where his thumb stops, Meera Fernandes runs a three-bedroom homestay in an old Portuguese-era house. She lists it on Airbnb and on two other travel sites, and the only thing joining the three calendars is a link to a calendar file each site reads every few hours. Her nightly price moves with demand while she sleeps. When Rohan pays in rupees, the money won't reach Meera until the business day after his family walks through her door, and a guest from London who stayed last week paid in pounds for the same rooms. If anything in this chain is wrong, a family arrives to a house that is already full, or a host is paid twice, or not at all.

Airbnb, from published numbers
  • 9 million+ — active listings worldwide, in 150,000+ cities and towns and 220+ countries and regions (Airbnb newsroom, last updated May 2026)
  • 5.5 million+ — hosts; 2.5 billion+ guest arrivals since 2007; $380 billion+ earned by hosts, all-time (Airbnb newsroom, May 2026)
  • 533 million — nights and seats booked in 2025, up from 492 million in 2024; gross booking value $91.3 billion (Airbnb Form 10-K for 2025)
  • ~50 currencies and ~20 local payment methods; the guest's payment is held until check-in and the payout to the host generally starts on the business day after the scheduled check-in (Airbnb Form 10-K for 2025)
  • $6.9 billion — cash held on behalf of hosts and guests at 31 December 2025 (Airbnb Form 10-K for 2025)
  • ~10,000 updates a second to listings, mostly pricing and availability; popular destinations can have ~100,000 available homes for one search (Airbnb, "Applying Embedding-Based Retrieval to Airbnb Search", January 2026)

For context:
Airbnb publishes its search ranking research, parts of its payments platform and its help-centre rules for calendars, pricing and reviews; those are cited where used
It doesn't publish search volume, bookings per day, polling rates or ledger sizes, so the estimates below state their own assumptions
Everything not in the list above is an illustrative design, not Airbnb's internal architecture

This is the system we'll design today.

ExplainerThe whole quest: Airbnb

Airbnb has 9 million+ active listings from 5.5 million+ hosts and booked 533 million nights and seats in 2025 ($91.3 billion); listings change ~10,000 times a second, mostly prices and availability. On stated assumptions that is ~365,000 reservations a day (~13 a second at peak) against ~14,000 search requests a second, and ~$6.9 billion held for hosts and guests at year end, about four weeks of bookings. Search leaves hold every listing's geo cell, a 365-night availability bit array and nightly prices in memory, filter by map rectangle and dates at query time, then retrieve with a two-tower model and an IVF index, rank with a deep model and re-rank the page, with fewer or smaller pins on the map. A uniqueness key on listing and night guards Airbnb's own bookings; iCal calendars imported every 3 hours are diffed per source, polled by risk and refreshed when a guest presses Reserve. Smart Pricing prices listing-nights in a nightly batch with a fast path, inside the host's range. Guests' money is held until check-in and paid out the business day after through an append-only double-entry ledger with idempotent calls. Bookings are risk-scored with targeted friction, and reviews stay sealed until both sides write or 14 days pass. The deep dives end with feeds that lie, prices that move at checkout, payout holidays and New Year's week in North Goa.

The Interview Begins#

You're interviewing at a company that runs a travel marketplace. The interviewer shares a screenshot: a map of North Goa with eighteen price pins, a calendar with three nights greyed out, and a payout screen that says "Expected Monday, 9 November".

Interviewer

"A guest searches, a host gets paid. Everything in between is yours. I don't want a booking-database exercise: tell me how the guest finds the right home, how we know it's free, what it costs tonight, and how the money moves."

Design the marketplace behind a global home-stays platform

Hosts list homes with photos, rules, a calendar and a nightly price. Guests search by place (or by dragging a map), dates and party size, and see homes that are free for those dates, ranked for them, with the total price. They book and pay; the host is paid after check-in; both review each other after the stay.

Requirements
  • Map and list search over millions of listings, filtered by the guest's dates at query time
  • Rank a few hundred thousand candidates down to the 18 on screen, in well under a second
  • Keep host calendars in step with other websites that sell the same home
  • Suggest nightly prices that follow demand, inside the host's limits
  • Collect from guests, hold until check-in, pay hosts in their currency, never twice
  • Score fraud risk at booking; collect two-sided reviews that neither side can retaliate against
Interviewer

"Leave out experiences, messaging, customer support and the host onboarding flow. The booking transaction itself, the reserve button and its database write, assume it works; I'll ask about it once and briefly."

Phase 1 · Stage 1 of 7 · 5 min budget · 2 min read

Phase 1: Requirements Clarification (5 minutes)#

You

"This is a two-sided marketplace, so I'll ask about both sides: how much supply, how much demand, how fast supply changes, and where the money sits."

Your Questions#

Transcript
You

"How big is the supply?"

Interviewer

"Airbnb says more than 9 million active listings from more than 5.5 million hosts, in over 150,000 cities and towns. Most hosts have one or two homes. Most listings are a single unit: a home is either free on a night or it isn't."

You

"How much is booked?"

Interviewer

"533 million nights and experience seats in 2025, with $91.3 billion of gross booking value. Airbnb doesn't publish searches or bookings per day."

You

"How often does a listing change?"

Interviewer

"Airbnb's search team wrote that listings receive around 10,000 updates a second, mostly availability and pricing, and that search is very sensitive to indexing delay."

You

"Who else writes to a host's calendar?"

Interviewer

"Other websites. Many hosts connect calendars by iCal: each site publishes an .ics link and imports the others'. Airbnb's help centre says an imported calendar updates automatically every 3 hours, and a host can press Refresh to pull it sooner. There's no push and no acknowledgement; it's a file you fetch."

You

"And the money?"

Interviewer

"The guest pays at booking, in about 50 currencies. The 10-K says Airbnb holds the full amount until check-in, then recognises its service fee and starts the payout to the host, generally on the business day after the scheduled check-in. At the end of 2025 it held $6.9 billion of cash on behalf of hosts and guests."

Functional Requirements#

Functional requirements
  • Search: by place name or map rectangle, dates (fixed or flexible), guests and filters; list and map results with a total price for the stay
  • Ranking: order candidates by how likely this guest is to book them and enjoy the stay; choose which ones become map pins
  • Calendars: host blocks, minimum stays and preparation days; import and export iCal feeds; a home is never confirmed for a night we know is taken
  • Pricing: host-set prices, or Smart Pricing: suggested nightly prices inside the host's minimum and maximum
  • Payments: charge the guest; hold until check-in; pay the host in their currency; refunds and alterations; a ledger that always balances
  • Trust: risk score every booking and add friction when needed; blind reviews from both sides after the stay

Non-Functional Requirements#

Non-functional requirements
Latency (design targets)
  • Search p99 < 800 ms end to end, including ranking; a map pan feels instant
  • A price or calendar change is searchable within ~1 minute
Scale (estimated below)
  • ~14,000 search requests a second at peak
  • ~10,000 listing updates a second into the search index (published)
Correctness
  • Zero confirmed double bookings from Airbnb's own reservations; iCal-caused ones measured and driven down
  • Zero money created or lost: every ledger entry balances; a payout is sent at most once

Phase 2 · Stage 2 of 7 · 5 min budget · 4 min read

Phase 2: Back of the Envelope Estimation (5 minutes)#

You

"Two published totals anchor everything: 533 million nights a year and $91.3 billion of bookings. Searches, stays per booking and polling rates are my assumptions, and I'll say so."

Your turn · checkpoint 1not answered

Before the numbers: what does a year of nights mean per day?

533 million nights and seats were booked in 2025. Treat them all as nights. How many nights is that a day? If the average stay is 4 nights (an assumption), how many reservations a day, and a second at a 3× peak? What does an average night cost, from $91.3 billion of bookings? And how long, on average, does Airbnb hold a guest's money, given that it held $6.9 billion at year end?

Think it through, or jot it down. Then compare.

One strong answer

  • Nights a day: 533 million ÷ 365 ≈ 1.46 million nights.
  • Reservations: 1.46 million ÷ 4 ≈ 365,000 a day, ~4 a second on average, ~13 a second at a 3× peak.
  • Per night: $91.3 billion ÷ 533 million ≈ $171, including cleaning and service fees.
  • Money held: $91.3 billion ÷ 365 ≈ $250 million booked a day; $6.9 billion ÷ $250 million ≈ 28 days of bookings held at any moment, roughly the average gap between paying and checking in (an estimate: year-end balances are seasonal).
  • So bookings are a trickle. The load is in search, the hard part of payments is holding billions correctly for weeks, and neither is about requests per second.

Search Estimation#

WorksheetSearch load (searches per day is an assumption)
Value
Search requests a day: every map drag, date change or filter is one (assumed) ~400 million
Average / peak at 3× ~4,600 / ~14,000 a second
Available homes for a popular destination (Airbnb) ~100,000
Candidates scored by retrieval, per big search (assumed) ~20,000
  • Ranking work: if each search scores ~20,000 homes cheaply and ~1,000 with the heavy model, peak is 14,000 × 1,000 = ~14 million heavy scores a second; Airbnb wrote that its search scores "millions of homes per second"
  • A single model pass over 100,000 homes per search is out: 14,000 × 100,000 = 1.4 billion a second

Conclusion: search must narrow fast and cheaply first (where, when, free) and spend the expensive model only on a shortlist.

Availability and Price Index#

WorksheetWhat search must hold in memory (estimates)
  • Availability: a one-unit home is free or not, per night: 365 nights = 365 bits ≈ 46 bytes a listing; 9 million listings ≈ ~0.4 GB
  • Nightly prices: 9 million × 365 nights × 4 bytes ≈ ~13 GB, plus minimum-stay and check-in-day rules
  • Updates: ~10,000 a second ≈ ~860 million a day, about 95 per listing per day on average, most of them price changes
  • Flexible dates: "±3 days" on both check-in and check-out is 7 × 7 = 49 date pairs per listing (Airbnb's example)

Conclusion: the whole calendar and price state fits comfortably in RAM across a few search shards. The challenge is not size; it is applying 10,000 changes a second and answering "free for all three nights, and at what total?" in microseconds per listing.

Calendars, Pricing and Money#

WorksheetBackground work (all estimates)
  • iCal feeds: if 30% of listings import 1.5 outside calendars each, that's ~4 million feeds. Every 3 hours: 4 million ÷ 10,800 s ≈ ~370 fetches a second. Every 15 minutes: ~4,500 a second
  • Smart Pricing: pricing every listing-night once a day is 9 million × 365 ≈ 3.3 billion predictions; in a 4-hour batch window, ~230,000 a second across a cluster
  • Payouts: ~365,000 check-ins a day → ~365,000 payout instructions a day in ~50 currencies
  • Ledger: ~8 balanced entries per reservation lifecycle (charge, fees, taxes, payable, payout, FX) ≈ ~3 million entries a day, all of which must sum to zero

Conclusion: polling 4 million calendar files is cheap; the danger is the hours between polls. Pricing is a batch problem. Payments are low volume and zero tolerance.

Key Metrics Summary#

Metric Value
Active listings / hosts (Airbnb, May 2026) 9M+ / 5.5M+
Nights booked in 2025 (Airbnb) 533 million (~1.46M a day)
Reservations at peak ~13 a second
Search requests at peak ~14,000 a second (assumed)
Listing updates into search (Airbnb) ~10,000 a second
Availability for all listings ~0.4 GB of bits
iCal fetches at a 3-hour cycle ~370 a second
Money held for hosts and guests (Airbnb, end 2025) $6.9 billion, ~28 days of bookings
Quick check3 questions · +5 XP each

Requirements and estimation

  1. Q1/3 Airbnb booked 533 million nights in 2025. At an assumed 4 nights a stay, roughly how many reservations a second at a 3× peak?

    Answer & explanation

    Answer: B

    533 million ÷ 365 ÷ 4 ≈ 365,000 a day, ~4 a second, ~13 at peak. The write path is small; search and money are the hard parts.

  2. Q2/3 Why can the availability of every listing for the next year sit in memory on search servers?

    Answer & explanation

    Answer: B

    Bits are small. Prices (~13 GB) are bigger but still fit across a few shards.

  3. Q3/3 $6.9 billion was held on behalf of customers at the end of 2025. What does that mean for the design?

    Answer & explanation

    Answer: B

    The guest pays at booking and the host is paid after check-in, so the platform is a custodian for about four weeks of bookings.

Phase 3 · Stage 3 of 7 · 10 min budget · 4 min read

Phase 3: High-Level Design (10 minutes)#

Your turn · checkpoint 2not answered

Sketch it first: Rohan drags the map over Assagao

Rohan's app sends a map rectangle, three nights and five guests. Which component works out which listings are inside the rectangle? Where does "free for all three nights" get checked, and where does the price for those nights come from? Which part decides the order, and which 18 become pins? And how did Meera's latest price, set by Smart Pricing an hour ago, and her other site's booking from this morning get into that answer?

Think it through, or jot it down. Then compare.

One strong answer

  • A search root receives the request and fans it out to search leaves, each holding a slice of listings with their location, availability bits, nightly prices and stay rules in memory.
  • Each leaf keeps only listings inside the rectangle, then only those free for all three nights that fit five guests and the minimum stay, computes each one's total, and runs a cheap retrieval model to keep its best few thousand, then the heavier first-stage ranker on those.
  • The root merges the leaves' top results, re-ranks the top set jointly (diversity, price spread) and picks which listings become full pins and which become small pins on the map.
  • Freshness: Smart Pricing and the calendar service publish changes to an event stream; an indexer applies them to the leaves within about a minute. Meera's other site reached the calendar service through the iCal poller, which fetched her imported feed at most 3 hours after that booking.
  • Not on this path: payments, reviews and fraud scoring. Search only reads.
You

"Four loops around one marketplace. Search reads a fast in-memory index. Calendars, prices and reviews write to their own services and stream changes into that index. Booking writes one small, strict record. Money moves on its own schedule, through a ledger, long after the search is forgotten."

System Architecture#

DiagramA stays marketplace: search reads, everything else streams in
%% title: A stays marketplace: search reads, everything else streams in
flowchart TD
  subgraph L1["Layer 1 · Guests and hosts"]
    direction LR
    GUEST@{ icon: "sd:phone", label: "Guest app<br/>map, dates, guests" }
    HOST@{ icon: "sd:laptop", label: "Host tools<br/>calendar, price range" }
    EXT@{ icon: "sd:globe", label: "Other travel sites<br/>.ics feeds" }
  end
  subgraph L2["Layer 2 · Read path"]
    direction LR
    API@{ icon: "sd:api", label: "API gateway" }
    ROOT["Search root<br/>fan out, merge, re-rank, pins"]:::hot
    LEAF["Search leaves<br/>geo + dates + retrieval + ranker"]:::core
  end
  subgraph L3["Layer 3 · Sources of truth"]
    direction LR
    CAL["Calendar service<br/>nights, blocks, rules"]:::service
    ICAL@{ icon: "sd:cron", label: "iCal poller<br/>import every few hours" }
    PRICE@{ icon: "tech:spark", label: "Smart Pricing<br/>Spark batch + demand" }
    RES["Reservations<br/>one row per stay"]:::service
  end
  subgraph L4["Layer 4 · Events and money"]
    direction LR
    BUS@{ icon: "tech:kafka", label: "Kafka<br/>listing changes, bookings" }
    PAY["Payments<br/>pay-in, ledger, payouts"]:::core
    RISK@{ icon: "sd:shield", label: "Risk scoring<br/>at booking" }
  end
  GUEST ==> API ==> ROOT
  ROOT ==> LEAF
  HOST --> CAL
  EXT -.->|"fetched"| ICAL --> CAL
  PRICE --> CAL
  CAL --> BUS
  BUS ==>|"~10,000 updates/s"| LEAF
  API --> RES
  RES --> RISK
  RES --> PAY
  RES --> CAL
  class GUEST,HOST client
  class EXT external
  class API edge
  class BUS queue
  class ICAL,PRICE service
  class RISK service

KEY INSIGHTS:

  • Search reads, it never asks. Location, availability, prices and rules for every listing live in the search leaves' memory, updated by events. A search doesn't call the calendar or pricing services
  • Each source of truth owns one thing. The calendar service owns nights, pricing owns suggestions, reservations own stays, payments own money; they talk through events, so one can be slow without stopping the others
  • Money runs on its own clock. A booking creates obligations; the ledger and payout scheduler turn them into money movements days or weeks later

The One Strict Write, Briefly#

Transcript
Interviewer

"You said you'd cover the reserve button once."

You

"Most listings are a single unit, so a night is free or taken, not a count. The reservation service writes the stay and one row per night into a stay_nights table whose primary key is (listing_id, night), in one transaction. If any night is already there, the insert fails and the guest sees 'just booked'. That's the whole guard for Airbnb's own bookings. The interesting double bookings are the ones caused by nights we don't know are taken, because another site sold them. That's Deep Dive 3."

Data Flow: Rohan Searches, Then Books#

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, North Goa
  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: Top results: free all 3 nights, total price
  S-->>R: 18 cards, 11 full pins, 7 mini-pins
  R->>B: Reserve Meera's home, quote id
  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, payout to Meera on 9 Nov

MEANWHILE, IN PARALLEL:

  • The reservation emits NightsBooked; the indexer clears those three bits in the leaf that holds Meera's listing within seconds, and her exported .ics feed is regenerated for the other sites to fetch
  • A review window for both Rohan and Meera is scheduled to open at check-out on 9 November
Quick check3 questions · +5 XP each

High-level design

  1. Q1/3 Where does a search check that a listing is free for all three nights?

    Answer & explanation

    Answer: B

    Calling a service per candidate would mean thousands of calls per search. The leaf holds the bits and checks them in microseconds.

  2. Q2/3 Two guests press Reserve on Meera's home for the same night at the same moment. What stops both succeeding?

    Answer & explanation

    Answer: C

    One unit per night means a uniqueness constraint is enough for Airbnb's own bookings.

  3. Q3/3 Why does Rohan's map show some listings as small pins without a price?

    Answer & explanation

    Answer: C

    Airbnb found map pins don't get position-ordered attention like a list; it limits or tiers pins instead.

Phase 4 · Stage 4 of 7 · 20 min budget · 23 min read

Phase 4: Deep Dives (20 minutes)#

Deep Dive 1: Geo Search With Dates at Query Time (Week 7 Concepts: Search Architecture; Week 1: Partitioning)#

You

"A web search engine matches words. We match a rectangle, a date range and a party size, against inventory that changes 10,000 times a second. The order of the filters is the whole design: where first, then when, then everything else."

This builds on Week 7, Day 1 · Search Fundamentals and Architecture and Week 1, Day 1 · Partitioning Deep-Dive.

Your turn · checkpoint 3not answered

A rectangle, three nights, five guests

Rohan's map covers about 6 × 4 km of North Goa, with ~2,500 listings inside. He wants 6 to 9 November (three nights: 6, 7 and 8), for five guests. How do you find the 2,500 quickly among 9 million? How do you test "free all three nights" in one step? What else makes a free home ineligible? And if he ticks "±3 days", how many date pairs must each listing be checked for?

Think it through, or jot it down. Then compare.

One strong answer

  • Where: index every listing by a geographic cell id (S2 or geohash). Turn the rectangle into a short list of covering cells, and read only listings in those cells. The 9 million become ~2,500 with no scan.
  • When: keep each listing's next 365 nights as a bit array, bit set = free. "Free on 6, 7 and 8 November" is a mask of 3 consecutive bits; one AND and a compare answers it.
  • Rules: a free run isn't always bookable: minimum stay (Meera wants 2 nights on weekends), allowed check-in days, a preparation day after each stay, maximum guests. Each is a few integer checks.
  • Price: the total is the sum of three nightly prices plus cleaning and fees, computed in the leaf for the survivors only.
  • Flexible dates: ±3 days on check-in and on check-out is 7 × 7 = 49 date pairs, Airbnb's own example. Precompute per listing which check-in days start a valid stay of each length, so the 49 checks become a few lookups.

What is published: Airbnb's search architecture is a root that fans out to leaf services, which do retrieval and ranking. Its retrieval paper notes that "almost all searches on Airbnb contain some type of filtering criteria (most commonly a geographic region filter if a user moves the map area)", that flexible dates of ±3 days create 49 check-in and check-out combinations, and that it built "a compact data structure of pre-computed availability and pricing" for a fixed number of days ahead, updated in near real time, with a final step that refreshes "accurate pricing and availability checks" before results leave the leaf.

  • Partition by listing, not by place. If leaves were split by region, Goa during a festival would melt one shard while others idle. Spreading listings evenly (by listing id) across leaves and fanning every search out to all of them keeps load even; the geo filter runs inside each leaf. Add replicas of the whole leaf set to add throughput.
  • Count before you draw. A rectangle over all of France holds hundreds of thousands of homes. The leaf caps how many candidates it passes on (by a cheap prior, such as recent booking rate) so the ranker's cost is bounded per leaf.
  • Freshness has a budget. An indexer consumes listing events (prices, blocks, bookings) and applies them to the leaf holding that listing, a few microseconds each: 10,000 a second is nothing for memory, everything for correctness. Track "event time to searchable" as a metric; target about a minute at p99.
  • Search may be slightly stale; booking isn't. A home booked a second ago can still appear. The reservation's unique key catches it, and the guest sees "just booked". Measure that rate; it is the cost of fast search.
pythonListing · 44 lines
# search/leaf_filter.py

"""
The cheap filters in a search leaf (illustrative). Every listing in memory has a
geo cell, a 365-night availability bit array (bit set = free), nightly prices and
stay rules. Survivors of where + when + rules go on to retrieval and ranking.
"""

from dataclasses import dataclass


@dataclass
class Listing:
    lid: int
    cell: int
    free: int               # bit i set = night (today + i) is free
    prices: list            # nightly price in listing currency, 365 entries
    min_nights: int
    max_guests: int
    checkin_days: set       # allowed weekdays, 0 = Monday
    cleaning_fee: int


def stay_mask(start: int, nights: int) -> int:
    return ((1 << nights) - 1) << start


def eligible(l: Listing, cells: set, start: int, nights: int, weekday: int, guests: int) -> bool:
    if l.cell not in cells or guests > l.max_guests:
        return False
    if nights < l.min_nights or weekday not in l.checkin_days:
        return False
    m = stay_mask(start, nights)
    return l.free & m == m                                  # free on every night of the stay


def total(l: Listing, start: int, nights: int) -> int:
    return sum(l.prices[start:start + nights]) + l.cleaning_fee


def search_leaf(listings, cells, start, nights, weekday, guests, cap=5000):
    out = [(l.lid, total(l, start, nights)) for l in listings
           if eligible(l, cells, start, nights, weekday, guests)]
    return out[:cap]                                         # bounded work for the ranker

Deep Dive 2: Retrieve, Then Rank, Then Choose the Pins (Week 7 Concepts: Query Processing and Relevance)#

You

"After the filters, a popular destination can still have tens of thousands of free homes. The guest will look at a dozen. Ranking is a funnel: a cheap model over many, an expensive model over few, and a final pass that looks at the page as a whole."

This builds on Week 7, Day 3 · Query Processing and Relevance.

Your turn · checkpoint 4not answered

From 20,000 free homes to 18 cards and a map

A search for all of Goa over a December weekend leaves 20,000 free homes. You have a deep model that predicts booking probability well but costs ~0.1 ms per home per core, and a 200 ms budget for ranking. How do you get to the 18 cards? What does the cheap first stage need to be fast? And why might showing all 18 as pins on the map hurt bookings?

Think it through, or jot it down. Then compare.

One strong answer

  • Funnel: a retrieval model scores all 20,000 cheaply and keeps ~1,000; the deep first-stage model scores those 1,000 (0.1 s of CPU, spread across leaves and cores); a setwise re-ranker at the root looks at the top ~100 together, for variety in price, style and location, and picks the 18.
  • Fast retrieval: a two-tower model. One tower turns each listing into a vector, computed offline once a day; the other turns the query (place, dates, guests) into a vector at request time. Scoring is a distance between vectors, and an approximate-nearest-neighbour index avoids even that for most homes.
  • Maps aren't lists. In a list, attention falls from the top; on a map it is spread over the pins. Airbnb found that pins of low booking probability waste the few clicks a guest makes: 95% of searchers click at most 12 pins. Showing fewer, better pins, or making the weaker ones small, raised bookings.

What is published: Airbnb moved search ranking from gradient-boosted trees to neural networks (KDD 2019). Its 2018 work learned 32-dimensional embeddings for 4.5 million active listings from 800 million search click sessions, for real-time personalization and similar-listing recommendations, which it said drive 99% of conversions. Its embedding-based retrieval (2026) uses a two-tower model whose listing tower is computed daily offline; it chose an inverted-file (IVF) index over HNSW because HNSW's memory grew too large under real-time updates and performed poorly with filters, and stores each listing's cluster id in the search index so retrieval is "a standard search filter". Euclidean distance gave balanced clusters where dot product didn't. The launches added 0.31% to booking conversion and cut compute by about 16%. For maps (KDD 2024), a "bookability filter" that drops pins far less likely to be booked than the top one raised uncancelled bookings by 1.9% in a test of 34 million searchers per arm; on desktop, where all 18 cards need pins, "mini-pins" without prices get about 8 times fewer clicks.

DiagramThe ranking funnel, each stage an order of magnitude smaller
%% title: The ranking funnel, each stage an order of magnitude smaller
flowchart TD
  F["Filtered by place, dates, rules<br/>~20,000 free homes"]:::service
  EBR["Retrieval · two towers, IVF clusters<br/>keep ~1,000"]:::core
  DNN["First-stage ranker · deep model<br/>booking probability"]:::core
  SET["Setwise re-ranker at the root<br/>top ~100 as a page"]:::hot
  PINS["Pins · bookability filter<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
  • Daily vectors, live facts. The listing tower can't use today's price, because it runs once a day. Retrieval uses yesterday's price features; the leaf's live prices and availability from Deep Dive 1 are applied before anything is shown. A stale vector only costs a little ranking quality, never a wrong price.
  • The query tower runs per request on the place, dates, party size and length of stay: one small inference, not one per listing.
  • IVF fits a filtered index. Cluster ids are just another field in the leaf; "nearest 8 clusters to this query" becomes a filter alongside the geo cells and availability. A graph index like HNSW would have to be updated thousands of times a second and searched around filters.
  • Train on trips, not single searches. A guest searches for weeks before booking. Airbnb's retrieval model treats the booked listing as the positive and homes the same guest saw or wishlisted on that trip, but didn't book, as hard negatives. Random negatives made the problem "too easy".
  • The re-ranker sees the page. Eighteen near-identical villas at the same price is a worse page than a mix. A setwise model scores the set, which a per-listing model can't.
pythonchoose_pins · 25 lines
# search/pins.py

"""
Choosing map pins (illustrative), after Airbnb's published "bookability filter":
attention on a map is spread across pins, so a pin far less likely to be booked
than the best one costs the guest a click. Listings below the threshold become
mini-pins on desktop (where every card needs a pin) or are left off on mobile.
"""

ALPHA = 1.0     # how far below the anchor's booking probability a full pin may fall (log scale)


def choose_pins(ranked: list, desktop: bool, max_pins: int = 18):
    """ranked: [(listing_id, p_book)] best first. Returns (full_pins, mini_pins)."""
    top = ranked[:max_pins]
    if not top:
        return [], []
    anchor = top[1][1] if len(top) > 1 else top[0][1]     # second-best: robust to one outlier
    full, mini = [], []
    for lid, p in top:
        if p * (10 ** ALPHA) >= anchor:
            full.append(lid)
        elif desktop:
            mini.append(lid)                               # still shown, drawn small, no price
    return full, mini
Quick check4 questions · +5 XP each

Search and ranking

  1. Q1/4 Why does each search leaf hold a slice of all listings instead of one region?

    Answer & explanation

    Answer: B

    Partition by listing id for even load; filter by geography inside each leaf.

  2. Q2/4 Rohan ticks "±3 days" for check-in and check-out. How many date pairs might each listing be checked for?

    Answer & explanation

    Answer: C

    7 possible check-ins × 7 possible check-outs. Precomputed valid stays per check-in day keep it cheap.

  3. Q3/4 Why did Airbnb pick an IVF index over HNSW for retrieval?

    Answer & explanation

    Answer: B

    Airbnb reported HNSW scored slightly better offline, but IVF fitted 10,000 updates a second and filtered search.

  4. Q4/4 95% of searchers click 12 map pins or fewer. What did Airbnb change?

    Answer & explanation

    Answer: B

    Pins don't get list-style attention; weak pins waste the few clicks a guest makes.

Deep Dive 3: Calendars That Other Websites Also Write (Week 5 Concepts: Conflict Resolution; Week 2: Distributed Cron)#

You

"Meera sells the same three bedrooms on three sites. iCal is the lowest common denominator: each site publishes a file of busy dates and reads the others' files on its own schedule. No push, no acknowledgement, no versions. Our job is to make the window in which we don't know as small as possible, and to never let a bad file do damage."

This builds on Week 5, Day 4 · Conflict Resolution and Week 2, Day 5 · Distributed Cron and Job Scheduling.

Your turn · checkpoint 5not answered

Meera's other site sells 7 November at 10:05

At 10:05 one morning a guest books Meera's home for 7 November on another site. Airbnb imports that site's .ics file on a 3-hour cycle and last fetched it at 09:50. When will Airbnb next see the booking, and what's the average blind window for a change like this? Suppose Rohan had pressed Reserve for 6 to 9 November at 11:30 that day: what happens, and what could you do at that moment? Then the other site has an outage and serves an empty file. What must the importer not do?

Think it through, or jot it down. Then compare.

One strong answer

  • Blind window: the next fetch is at 12:50, so Airbnb is blind for 2 h 45 min. A change lands at a random point in a 3-hour cycle, so the average blind window is ~1.5 hours and the worst is 3.
  • 11:30 without help: Airbnb still shows the night as free; Rohan's three nights insert cleanly (nothing on Airbnb holds 7 November) and Meera has two guests for one house.
  • Refresh on intent: when a guest presses Reserve on a listing that imports calendars, and the last import is older than a few minutes, fetch the feeds right then, with a short timeout and a conditional request. A 304 "not modified" costs milliseconds; a changed file is applied first, and Rohan sees "these dates are no longer available" instead of a cancellation two days later.
  • The empty file: never treat an empty or unparseable feed as "everything is free". Keep the last good snapshot, mark the feed unhealthy, alert the host, and only accept a large drop in blocked nights after it is seen twice.

What is published: Airbnb's help centre says an imported calendar "automatically updates every 3 hours", that a host can select Refresh to check sooner, that only links ending in .ics work, and that nights blocked or booked on Airbnb are blocked on the other site's calendar through the export link. The format itself is iCalendar (RFC 5545), where each busy period is an event with a stable UID.

DiagramImporting and exporting calendars without trusting the file
%% title: Importing and exporting calendars without trusting the file
flowchart TD
  OTHER@{ icon: "sd:globe", label: "Other site<br/>.ics feed" }
  SCHED@{ icon: "sd:cron", label: "Poll scheduler<br/>due feeds first" }
  FETCH["Fetcher<br/>If-None-Match, timeout"]:::service
  DIFF["Snapshot diff<br/>per source, sanity checks"]:::core
  CAL["Calendar service<br/>blocks tagged by source"]:::hot
  EXP@{ icon: "sd:cdn", label: "Our .ics export<br/>rebuilt on change" }
  SCHED ==> FETCH
  OTHER -.->|"304 or new file"| FETCH
  FETCH ==> DIFF ==> CAL
  CAL ==> EXP
  EXP -.->|"fetched by them"| OTHER
  class OTHER external
  class SCHED service
  class EXP edge
  • Tag every block with its source. A block imported from feed F is replaced only by the next good snapshot of F. It can never erase an Airbnb reservation or a host's manual block, and two feeds can't overwrite each other. Diff by event UID and date range, not by "the whole calendar".
  • Poll by risk, not round-robin. The cost of staleness is concentrated: a listing with free nights in the next few weeks, on dates people are searching, whose feed changed recently. Score each feed by that and poll the riskiest every few minutes, the rest on the slow cycle. ~370 fetches a second at 3 hours is the floor, not the ceiling.
  • Conditional requests make frequent polling cheap. Send the last ETag or Last-Modified; most fetches return 304 with no body. Respect each site's rate limits and back off per host name, not globally.
  • Fetch on intent. The Reserve button is the one moment staleness turns into a double booking. A synchronous refresh capped at ~1 second covers it; if the other site doesn't answer, book anyway and flag the stay for the host to confirm, rather than failing the guest.
  • Our export is somebody else's poll. Rebuild Meera's .ics within seconds of a booking and serve it from a CDN with a short cache time; then the other site's blind window is its own poll interval, not ours plus theirs.
  • Where partners offer an API that pushes changes, use it instead of iCal; that is a different integration with its own ordering problems, and this design treats iCal as the fallback every host can use.
pythonSuspiciousFeed · 30 lines
# calendar/ical_import.py

"""
Applying one imported .ics snapshot (illustrative). Blocks are tagged with their
source feed; a snapshot replaces only that feed's blocks, never reservations or
manual blocks; suspicious snapshots are held back instead of unblocking nights.
"""

MAX_DROP = 0.5          # a snapshot that frees over half the feed's blocked nights needs a second look


class SuspiciousFeed(Exception):
    pass


def apply_snapshot(cal, listing_id: int, feed_id: str, events: list, confirmed_drop: bool = False):
    """events: [(uid, first_night, last_night)] parsed from the feed (end date exclusive in .ics)."""
    new = {(uid, d) for uid, a, b in events for d in cal.nights(a, b)}
    old = cal.blocks(listing_id, source=feed_id)                     # {(uid, night)}
    if not events and old:
        raise SuspiciousFeed("empty feed: keep the last good snapshot")
    freed = {n for _, n in old} - {n for _, n in new}
    if old and len(freed) > MAX_DROP * len({n for _, n in old}) and not confirmed_drop:
        raise SuspiciousFeed("large drop: confirm on the next fetch before unblocking")
    with cal.tx(listing_id):
        for uid, night in new - old:
            cal.block(listing_id, night, source=feed_id, uid=uid)    # a clash with our own stay is flagged
        for uid, night in old - new:
            cal.unblock(listing_id, night, source=feed_id, uid=uid)  # only this feed's blocks
    cal.emit_changes(listing_id)                                     # to the search indexer

Deep Dive 4: Smart Pricing, a Suggestion With Guardrails (Week 8 Concepts: Streaming vs Batch)#

You

"There's no market price for Meera's house. No other listing is identical, so there's no demand curve to read off. Pricing has to learn how likely each night is to be booked at a price, then stay inside limits the host trusts, and then reach search without breaking a price a guest has already seen."

This builds on Week 8, Day 2 · Streaming vs Batch Processing.

Your turn · checkpoint 6not answered

Pricing the Diwali weekend

Meera has turned on Smart Pricing with a minimum of ₹9,000 and a maximum of ₹16,000 a night. Normally her weekends sell at ₹11,000. Three weeks before Diwali, searches for North Goa on those dates triple. What signals tell the model that 7 November is now worth more? What runs in a nightly batch and what reacts faster? What stops the suggestion going to ₹40,000, or swinging every hour? And what does Rohan pay if the price rises while he is on the checkout page?

Think it through, or jot it down. Then compare.

One strong answer

  • Signals: for each listing-night, the lead time, the season and weekday, the listing's own booking history, and demand for its area and dates: searches, views and bookings of similar homes, and how many nearby homes are still free.
  • Batch and fast path: the full model prices every listing-night once a day in a batch job. A demand monitor watches searches by area and date; when demand for Goa on 6 to 9 November jumps, it re-prices just those listing-nights within the hour.
  • Guardrails: the suggestion is clamped to Meera's ₹9,000–₹16,000; each night's price can move only so far per update; prices for nights inside a guest's open quote are left alone.
  • Rohan's price: the search returns a signed quote with the total and a short expiry. Checkout honours the quote; if it expired, he sees the new total before paying. A price never changes silently between "Reserve" and the charge.

What is published: Airbnb's pricing paper (KDD 2018) describes three components: a classifier that predicts "the booking probability of each listing-night", a regression model that predicts the optimal price for each listing-night with a customised loss, and personalization logic on top, deployed to power Price Tips and Smart Pricing. It notes there are no "identical" products on Airbnb, which makes conventional demand-curve pricing very difficult. Airbnb's help centre says Smart Pricing "uses hundreds of factors about your listing and your area to adjust your nightly price based on demand", within a minimum and maximum the host sets.

DiagramSmart Pricing: a batch model, a fast path and guardrails
%% title: Smart Pricing: a batch model, a fast path and guardrails
flowchart TD
  LOGS@{ icon: "sd:database", label: "Searches, views, bookings<br/>by area and date" }
  BATCH@{ icon: "tech:spark", label: "Spark nightly batch<br/>3.3B listing-nights" }
  DEM["Demand monitor<br/>area-date spikes"]:::service
  MODEL["Booking probability<br/>then price strategy"]:::core
  GUARD["Guardrails<br/>host min-max, step limit, open quotes"]:::hot
  CAL["Calendar service<br/>nightly price"]:::service
  LOGS ==> BATCH ==> MODEL
  LOGS -.-> DEM -.->|"re-price these nights"| MODEL
  MODEL ==> GUARD ==> CAL
  class LOGS store
  • Price from probability. For a listing-night, estimate P(booked | price, lead time, demand); the strategy model picks a price that trades a lower chance of booking against a higher price. Far ahead, with time to sell, it can ask more; close in with the night still free, less.
  • Batch is the backbone. 3.3 billion listing-nights once a day is a few hours on a cluster, and most nights don't need more. The fast path re-prices only the slices where demand moved: a festival announced, a long weekend filling up.
  • Every price change is a search update. Prices are the bulk of the ~10,000 updates a second. Rate-limit per listing (no night changes more than a few times a day) so the index and the host's calendar don't churn.
  • Hosts must trust it. Show the suggestion and why ("demand for your area on these dates is high"); never cross the host's limits; let them pin a price on any night.
  • Evaluate offline before A/B. Airbnb describes new offline metrics for pricing; the general idea is to check suggested prices against what actually booked, and at what price, before a host ever sees them.
pythonapply_suggestion · 24 lines
# pricing/guardrails.py

"""
Turning a model's suggested nightly price into the price a listing shows
(illustrative). The model proposes; the host's range, a per-update step limit
and open guest quotes decide what is actually applied.
"""

MAX_STEP = 0.15         # at most ±15% per update for one night


def apply_suggestion(night, suggested: float, host_min: float, host_max: float,
                     current: float, open_quotes: int, pinned: bool) -> float:
    if pinned:
        return current                                   # the host set this night by hand
    if open_quotes > 0:
        return current                                   # a guest is checking out at this price
    target = min(max(suggested, host_min), host_max)     # never outside the host's range
    lo, hi = current * (1 - MAX_STEP), current * (1 + MAX_STEP)
    return round(min(max(target, lo), hi))               # move gradually toward the target


# Meera, 7 Nov: model says 19,000; range 9,000-16,000; today 11,000
# apply_suggestion(n, 19000, 9000, 16000, 11000, 0, False) -> 12650, then 14547, then 16000

Deep Dive 5: Payments as a Marketplace: Hold, Ledger, Pay Out (Week 2 Concepts: Idempotency; Week 3: Audit Logs)#

You

"A shop takes money and keeps it. A marketplace takes money that mostly isn't its own, holds it for weeks, and pays it out to someone else, in another currency, on another country's business day. The ledger is the product; the payment providers are just how cash enters and leaves it."

This builds on Week 2, Day 2 · Idempotency in Practice and Week 3, Day 5 · Audit Log System.

Your turn · checkpoint 7not answered

₹38,400 from Rohan, a payout to Meera

Rohan pays ₹38,400 on 13 October for three nights from Friday 6 November: ₹33,000 for the host (nights and cleaning), a guest service fee and taxes. The money arrives through a payment provider two days later. Meera is paid on the business day after check-in. Last week a London guest paid £300 for her home. What accounts would you keep, and which entries does each event post? What makes a retried payout safe? And what changes if Monday 9 November is a bank holiday?

Think it through, or jot it down. Then compare.

One strong answer

  • Accounts, double entry: cash in transit from the provider; funds held for hosts (a liability: it's Meera's money once she hosts); unearned service fees; taxes payable; payouts in transit. Every event posts entries that sum to zero.
  • At booking (13 Oct): provider receivable +38,400; funds held for Meera −33,000; unearned fees −the fee; taxes payable −the tax. On settlement: cash at bank in, provider receivable out. At check-in (6 Nov): unearned fee → revenue; the host's share becomes payable. On payout: payable to Meera out, bank cash out, with the payout id.
  • Idempotent payout: each payout has a deterministic key (host, stay, attempt) recorded before the call to the payout provider; a retry with the same key returns the first result instead of sending money twice.
  • Currencies: the London guest's entries are in pounds, Meera's payable in rupees; the conversion is its own pair of entries at a recorded rate, so each currency balances on its own.
  • Weekends and holidays: "business day after check-in" is computed on Meera's payout country's calendar. Check-in is a Friday, so the payout is due Monday 9 November; a bank holiday that Monday moves it to Tuesday. The scheduler reads a holiday calendar per country.

What is published: Airbnb's 10-K says it holds "the total amount the guest has paid until check-in", then recognises its service fee and starts the payout, which "generally occurs on the business day after the scheduled check-in", in about 50 currencies; it held $6.9 billion of such cash at the end of 2025. Its engineering blog describes a payments platform split into subdomains including Pay-in, Payout, Processing, Ledger, and Settlement and Reconciliation, and 20+ local payment methods added in 14 months, some confirmed asynchronously by webhook after the guest pays in another app. Its idempotency library, Orpheus (2019), splits each payment request into pre-RPC, RPC and post-RPC phases, with no service calls in the pre- and post-RPC phases and no database work during the RPC, reads idempotency state from the primary database, and classifies failures as retryable or not.

DiagramMoney in, held, then out: every step a balanced ledger entry
%% title: Money in, held, then out: every step a balanced ledger entry
flowchart TD
  G@{ icon: "sd:card", label: "Rohan pays<br/>card or local method" }
  PSP@{ icon: "sd:bank", label: "Payment provider<br/>settles in 1–2 days" }
  PAYIN["Pay-in<br/>idempotent charge"]:::service
  LED["Ledger<br/>double entry, append-only"]:::hot
  HOLD@{ icon: "sd:wallet", label: "Funds held for hosts<br/>a liability until check-in" }
  SCHED@{ icon: "sd:cron", label: "Payout scheduler<br/>business day after check-in" }
  OUT["Payout<br/>idempotent per payout id"]:::core
  M@{ icon: "sd:bank", label: "Meera's bank<br/>in rupees" }
  G ==> PAYIN ==> PSP
  PAYIN ==> LED
  LED --> HOLD
  SCHED ==> OUT ==> M
  OUT ==> LED
  class G client
  class PSP,M external
  class HOLD store
  class SCHED service
  • The ledger is append-only. A refund or a changed booking is new entries that reverse or adjust, never an edit. The balance of any account at any time is a sum, and auditors can replay it.
  • Three phases per external call. Before the call, write the intent and its key in one local transaction; make the call with nothing else open; after it, record the result. A crash between phases leaves a row that a retry can resume from, and a retry never re-sends money the provider has already accepted.
  • Async payment methods wait in a state. A guest who pays in a wallet app is PAYMENT_PENDING until the provider's webhook arrives; webhooks are deduplicated by event id, and a sweeper asks the provider about any payment pending too long.
  • Reconcile against the outside world daily. Provider settlement files and bank statements are matched, line by line, to ledger entries. Anything unmatched is a ticket, not a rounding error.
  • Payouts are batched per host per day and per payout method. 365,000 a day is small; the care goes into country calendars, payout-method failures (a closed bank account moves the money back to "payable", not into the void) and tax withholding rules.
pythonUnbalanced · 40 lines
# payments/ledger.py

"""
A double-entry ledger and an idempotent payout (illustrative). Every posting is
a set of entries in one currency that sums to zero; postings are append-only and
keyed so a retried event can't post twice.
"""

from decimal import Decimal


class Unbalanced(Exception):
    pass


def post(db, key: str, currency: str, entries: list) -> None:
    """entries: [(account, amount)], positive = debit, negative = credit."""
    if sum(Decimal(a) for _, a in entries) != 0:
        raise Unbalanced(key)
    with db.tx():
        if db.exists("postings", key=key):
            return                                       # the same event, delivered again
        db.insert("postings", key=key, currency=currency)
        for account, amount in entries:
            db.insert("entries", posting=key, account=account, amount=Decimal(amount))


def pay_out(db, provider, host_id: str, stay_id: str, amount: Decimal, currency: str):
    key = f"payout:{stay_id}"
    row = db.primary.get("payouts", key=key)             # read the primary, never a replica
    if row and row.status in ("SENT", "CONFIRMED"):
        return row                                       # retried after a crash: already done
    if not row:
        db.primary.insert("payouts", key=key, host=host_id, amount=amount, status="INTENT")
    result = provider.send(idempotency_key=key, to=host_id, amount=amount, currency=currency)
    status = "SENT" if result.ok else ("INTENT" if result.retryable else "FAILED")
    db.primary.update("payouts", key=key, status=status, ref=result.ref)
    if result.ok:
        post(db, key, currency, [(f"payable:{host_id}", amount), ("cash:payouts_in_transit", -amount)])
    return db.primary.get("payouts", key=key)

Deep Dive 6: Risk Scoring at Booking and Two-Sided Blind Reviews (Week 9 Concepts: Security Architecture)#

You

"A marketplace of strangers runs on trust, and trust has two mechanisms here: stop the bad booking before it happens, and make reviews honest enough that the next guest and host can rely on them."

This builds on Week 9, Day 5 · Security Architecture.

What is published: Airbnb's 10-K lists "host and guest reviews, account protection, risk scoring, secure payments", "fraud and scam prevention" and "anti-party technology" among the components of its system of trust. Airbnb's trust team wrote in 2018 about "targeted friction" against chargebacks, such as micro-authorisations, 3-D Secure and billing-statement verification, chosen by comparing the cost of false positives with fraud losses, which Airbnb absorbs rather than passing to hosts. In 2014 Airbnb changed reviews so that neither side sees the other's review until both have written one or the 14-day window, shortened from 30 days, has passed.

DiagramA stay's two reviews, sealed until both are in or 14 days pass
%% title: A stay's two reviews, sealed until both are in or 14 days pass
stateDiagram-v2
  [*] --> WINDOW_OPEN: check-out
  WINDOW_OPEN --> ONE_SEALED: first review submitted
  ONE_SEALED --> REVEALED: second review submitted
  ONE_SEALED --> REVEALED: 14 days pass
  WINDOW_OPEN --> CLOSED_EMPTY: 14 days, no reviews
  REVEALED --> [*]
  CLOSED_EMPTY --> [*]
  class REVEALED ok
  class ONE_SEALED queue
  • Score, then decide with friction. A booking from a three-day-old account, with a card from one country and a device in another, for one night on New Year's Eve, four hours ahead, scores high. Low scores pass; middle scores get friction a real cardholder clears and a fraudster can't (micro-authorisations, 3-D Secure, ID verification); high scores are blocked or reviewed.
  • Set the threshold by cost. A false positive loses a real booking; a false negative becomes a chargeback the platform pays. Pick the cut-off that minimises the sum, per market and night.
  • Party risk is its own signal. A last-minute, one-night, whole-home booking on New Year's Eve by a new account is a different risk from a stolen card; require more history for it.
  • Why blind reviews: if Rohan reviews the broken hot water first, Meera can read it and retaliate about the messy kitchen, so both hold back. Sealing both until both are in, or 14 days pass, means neither can react to the other.
  • Scoring sits on the booking path, so it has a hard latency budget and a fallback: if the model service is slow, apply a conservative rule set rather than block every booking or wave every booking through.
  • Features are precomputed. Account age, past chargebacks, device history and velocity counters are kept in a feature store updated by events; at booking, the model reads a few keys and runs.
  • Friction is a state, not a failure. The reservation waits in VERIFYING while the guest completes the challenge; the nights are held by the reservation row from Phase 3, with a short timeout.
  • The reveal is a durable timer. At check-out, schedule a reveal job for check-out + 14 days; submitting the second review triggers it early. The job is idempotent (reveal twice = reveal once), and after reveal neither review can be edited.
  • Ratings feed search. A revealed review updates the listing's rating and review count, which arrive in the search leaves as one more listing event.
pythondecide · 22 lines
# trust/booking_risk.py

"""
Risk decision at booking (illustrative). A model score plus a fallback rule set;
the cut-offs come from the expected cost of a wrong decision, so a high-value
night with a new account gets friction sooner than a routine stay.
"""

CHARGEBACK_COST = 1.0      # cost per unit of booking value if fraud gets through
FRICTION_LOSS = 0.08       # share of good guests who abandon at a challenge (assumed)


def decide(score: float, value: float, model_ok: bool, rules) -> str:
    if not model_ok:
        return rules.fallback()                          # never fail open or closed on a timeout
    expected_fraud = score * value * CHARGEBACK_COST
    expected_friction = (1 - score) * value * FRICTION_LOSS
    if score > 0.9:
        return "BLOCK"
    if expected_fraud > expected_friction:
        return "FRICTION"                                # micro-auth, 3-D Secure or ID check
    return "ALLOW"

Data infrastructure, briefly: every deep dive above leans on the same events (searches, views, bookings, prices, reviews) landing in a warehouse for training and evaluation. Airbnb built Minerva so that a metric like nights booked is defined once and computed the same way for dashboards and experiments; whichever tool you use, define the marketplace's core metrics in one place, or ranking and pricing teams will argue from different numbers.

Quick check6 questions · +5 XP each

Calendars, pricing, money and trust

  1. Q1/6 Airbnb imports an outside calendar every 3 hours. On average, how long is it blind to a booking made on the other site?

    Answer & explanation

    Answer: B

    The booking lands at a random point in the cycle: half the cycle on average, the whole cycle at worst.

  2. Q2/6 The other site has an outage and serves an empty .ics file. What should the importer do?

    Answer & explanation

    Answer: B

    An empty or sharply shrunk feed is more likely broken than true. Freeing nights on bad data causes double bookings.

  3. Q3/6 Smart Pricing suggests ₹19,000 for a night whose host maximum is ₹16,000. What is shown?

    Answer & explanation

    Answer: B

    Suggestions are clamped to the host's range and stepped so prices don't swing.

  4. Q4/6 A payout call times out and the worker retries. What prevents paying Meera twice?

    Answer & explanation

    Answer: B

    Idempotency keys make the retry a lookup. Reconciliation catches what slips through, but too late for the host who was paid twice.

  5. Q5/6 A booking scores in the middle: probably fine, possibly a stolen card. What's the best response?

    Answer & explanation

    Answer: C

    Targeted friction separates good guests from fraudsters without losing most good bookings.

  6. Q6/6 Why are reviews hidden until both sides submit or 14 days pass?

    Answer & explanation

    Answer: B

    Simultaneous reveal removes the reason to hold back an honest review.

Phase 5 · Stage 5 of 7 · 5 min budget · 2 min read

Phase 5: Scaling and Edge Cases (5 minutes)#

Interviewer

"Walk me through the Diwali weekend from Meera's side, then the cases that bite."

Meera's Diwali Weekend#

Meera's homestay over the Diwali long weekend (the payout timing and calendar rules follow Airbnb's published ones; the rest illustrative)
  • 13 Oct, 22:10 · Rohan reserves
    • The search leaf showed 6–9 Nov free; the reserve-time refresh of Meera's two imported feeds returns 304 and 304; three nights inserted; ₹38,400 charged and held
  • 13 Oct, 22:11 · The other sites learn
    • Meera's .ics export is rebuilt and served; the other sites block the nights on their next poll
  • 20 Oct · Demand spikes
    • Searches for North Goa on the Diwali dates triple; the fast path re-prices her remaining free nights toward ₹16,000 in steps
  • 6 Nov, 15:00 · Check-in
    • The service fee becomes revenue; Meera's share becomes payable; her payout is scheduled for the next business day in India, Monday 9 November
  • 9 Nov · Check-out
    • Both review windows open for 14 days; Meera's review stays sealed until Rohan writes his

Edge Cases#

Edge cases
Edge case 1: A feed that lies

Problem: Another site's export breaks and serves an empty calendar, or one with every date shifted by a timezone bug. Applied naively, it frees months of nights that are really taken.

Solution: Source-tagged blocks, a sanity check on every snapshot (no empty feed replaces a non-empty one; a large drop must be seen twice), the last good snapshot kept, and a host alert. Parse dates as all-day values in the listing's timezone.

Edge case 2: The price moves during checkout

Problem: Rohan saw ₹38,400; Smart Pricing raises the night while he enters his card.

Solution: Search returns a signed quote (listing, nights, total, currency, expiry). Checkout charges the quote; pricing skips nights inside an open quote; an expired quote shows the new total before payment.

Edge case 3: A festival concentrates traffic on one place

Problem: Goa's searches triple and its hosts' prices and calendars change constantly.

Solution: Leaves are partitioned by listing id, not place, so Goa's listings are spread across every leaf and no shard is hot. Add replicas of the whole leaf set for throughput. Poll Goa's iCal feeds more often for those dates.

Edge case 4: Payout day is a holiday, or the bank account is closed

Problem: The business day after check-in isn't a business day in Meera's country; or her bank returns the transfer.

Solution: A holiday calendar per payout country decides the date. A returned payout posts reversing entries back to "payable" and asks the host to update the method; the money is never "lost in transit".

Quick check3 questions · +5 XP each

Scaling and edge cases

  1. Q1/3 Searches for Goa triple during a festival. Why doesn't one search shard melt?

    Answer & explanation

    Answer: B

    Partitioning by place would create a hot shard; partitioning by listing spreads it.

  2. Q2/3 Smart Pricing raises a night while Rohan is checking out. What does he pay?

    Answer & explanation

    Answer: B

    A signed quote with an expiry makes the price the guest saw the price the guest pays.

  3. Q3/3 Meera's bank returns a payout. Where does the money go in the ledger?

    Answer & explanation

    Answer: C

    Every state of the money is an account. A failed transfer is a posting, not a mystery.

Phase 6 · Stage 6 of 7 · 2 min

Phase 6: Monitoring and Operations#

You

"Five questions: can guests find homes, is what they find true, are calendars fresh, is the money right, and is trust holding?"

Monitoring Dashboard#

Marketplace dashboard — 21:30 IST, a Friday in October (illustrative)
Search
Metric Now
Search requests 11,800/s
Search p99, end to end 640 ms
Listing event to searchable, p99 48 s
"Just booked" at reserve, share of attempts 0.6%
Calendars and pricing
  • Imported feeds: 3.9 million; fetches 1,240/s, of which 83% return 304
  • Age of imported calendars at reserve time, p95: 7 min (after refresh on intent)
  • Feeds held back as suspicious in the last hour: 214
  • Smart Pricing suggestions clamped by host limits today: 31%
Money and trust
  • Ledger imbalance: 0 (any non-zero pages someone)
  • Payouts due today sent on time: 99.7%; returned payouts: 0.4%
  • Bookings with friction: 2.1%; with friction then completed: 74%

SLOs for the Marketplace#

SLOs for search, calendars and money
SLO 1: Search answers fast and true
  • Target: p99 < 800 ms; listing changes searchable within 60 s at p99; "just booked" at reserve under 1% of attempts
SLO 2: Calendars are fresh where it matters
  • Target: imported calendars are under 10 minutes old at reserve time for 95% of reservations; double bookings caused by imports, per 10,000 stays, trending down every quarter; double bookings from Airbnb's own reservations: zero
SLO 3: Money is exact
  • Target: ledger imbalance zero at all times; 99.5% of payouts sent on the scheduled business day; every provider settlement line reconciled within 2 days
SLO 4: Reviews reveal on time
  • Target: 99.9% of review pairs revealed within 1 hour of the second submission or the 14-day deadline
Quick check3 questions · +5 XP each

Reading the dashboard

  1. Q1/3 "Listing event to searchable" rises from 48 s to 15 minutes. What will guests notice first?

    Answer & explanation

    Answer: B

    Search still answers fast, but from stale data. The reserve-time check catches it, at the cost of disappointed guests.

  2. Q2/3 The share of feed fetches returning 304 drops from 83% to 10% overnight. Likeliest cause?

    Answer & explanation

    Answer: B

    A collapse in 304s with steady booking activity points to a change in the other site's caching headers.

  3. Q3/3 Ledger imbalance shows ₹1,200 for a few seconds, then 0. What do you do?

    Answer & explanation

    Answer: A

    A balanced ledger should never be visibly unbalanced, even briefly. A transient imbalance is a bug in how postings are written.

Final challenge · Stage 7 of 7 · 2 min

Final Challenge: New Year's Week in North Goa#

The interviewer leans back. "One more."

Interviewer

"It's 1 December. New Year's week in North Goa is the busiest week of the year for that region. Hosts are changing prices by the hour, a lot of them also sell on other sites, and last year a ring of stolen cards targeted exactly these nights. Tell me what you'd change for the week, and what you'd measure."

The New Year's brief
  • Listings in North Goa: 40,000 (assumption); 60% import at least one outside calendar, 1.5 feeds each
  • Searches for Goa over 28 Dec–2 Jan: 10× a normal week (assumption)
  • Reservations for those nights: ~2,000 a day in December, many booked within 48 hours of arrival (assumption)
  • Smart Pricing wants to raise some nights 3× above the host's usual weekend price
  • Last year's fraud ring: new accounts, last-minute, one or two nights, whole homes
  • 25,000 check-ins on 31 December (assumption); payouts due the next business day
Your turn · checkpoint 8not answered

What do you change for the week?

Work out the iCal polling you'd need to keep imported calendars under 15 minutes old for these listings and dates, whether search capacity needs to change, what pricing should and shouldn't do, how risk thresholds change, and what happens on payout day.

Think it through, or jot it down. Then compare.

One strong answer

  • iCal: 40,000 × 60% × 1.5 ≈ 36,000 feeds. Every 15 minutes is 36,000 ÷ 900 s = 40 fetches a second, on top of ~370 a second globally: trivial for us. The limit is the other sites' rate limits, so poll by risk (free nights in the window, recent changes) and keep refresh on intent at reserve. Expected blind window drops from ~1.5 hours to ~7 minutes.
  • Search: Goa is spread across every leaf, so no shard is hot; 10× Goa searches is a small share of global traffic. Add leaf replicas if the global peak rises, nothing Goa-specific.
  • Pricing: let the model move prices up to each host's maximum, but keep the per-update step limit and never touch nights inside open quotes; show hosts why. A 3× jump above their usual price only happens if their maximum allows it.
  • Fraud: for whole-home bookings in Goa for 30 Dec–1 Jan made within 48 hours by accounts under 30 days old, lower the friction threshold and require ID verification; watch the friction-completion rate so good guests aren't driven away.
  • Payout day: 25,000 payouts the next business day in India; scheduled from 31 Dec, batched per host, idempotent per stay. Reconcile on 2 Jan before the next batch.
  • Measure: imported-calendar age at reserve, iCal-caused double bookings per 10,000 Goa stays, friction rate and completion, chargebacks on those nights over the following 90 days, and payouts sent on time.
Quick check4 questions · +5 XP each

Final challenge · Quest check

  1. Q1/4 36,000 imported feeds polled every 15 minutes. How many fetches a second?

    Answer & explanation

    Answer: B

    36,000 ÷ 900 seconds = 40. Polling isn't the cost; partners' rate limits and the blind window are what matter.

  2. Q2/4 Goa searches are 10× normal. What does search need?

    Answer & explanation

    Answer: B

    Partitioning by listing means a hot destination is everyone's small load, not one shard's big one.

  3. Q3/4 Smart Pricing wants 3× a host's usual price. What decides whether that happens?

    Answer & explanation

    Answer: B

    Hosts set the range; the system moves within it.

  4. Q4/4 Which new-account bookings get extra friction for New Year's Eve?

    Answer & explanation

    Answer: C

    Target the pattern last year's fraud ring used; leave routine bookings alone.

Wrap-up · 6 min

Interview Conclusion#

Transcript
Interviewer

"Why not ask the calendar service for availability during search? It's the source of truth."

You

"Because a big search has tens of thousands of candidates, at thousands of searches a second. The source of truth answers the one question that must be exact, at reserve time, through a uniqueness constraint. Search reads a copy in memory that is a minute old at worst, and we measure how often that minute disappoints someone."

Interviewer

"And why not push to the other sites instead of this polling?"

You

"Where a partner offers an API that pushes, we should use it. iCal is what every host can use without any software, so we make it as good as polling can be: risk-based schedules, conditional requests, refresh at reserve, and files we never trust blindly."

Interviewer

"The single most important decision?"

You

"Separate what must be exact from what must be fast, and put each where it belongs. Availability and price live in the search index for speed, with a uniqueness guard and a reserve-time refresh for exactness. Money lives in an append-only, double-entry ledger with idempotent calls to the outside world, because it is held for weeks and paid to someone else."

Summary: Concepts Applied from 10-Week Course#

Concepts from the 10-week course in a stays marketplace
  • Week 1: Data at scale
    • Partitioning — search leaves by listing id so a festival never makes a hot shard
  • Week 2: Failure-first design
    • Idempotency — payout and charge keys, three-phase external calls; distributed cron — iCal polling by risk, review reveals, payout days
  • Week 3: Messaging and async processing
    • Listing events into the search index; audit-grade, append-only ledger postings; webhooks for async payment methods
  • Week 5: Consistency and coordination
    • Conflict resolution — source-tagged calendar blocks; a uniqueness constraint for one-unit nights
  • Week 7: Search systems
    • Geo cells and date masks at query time; retrieve, rank, re-rank; map pins
  • Week 8: Analytics pipelines
    • Smart Pricing as a nightly batch with a fast path; metrics defined once
  • Week 9: Security
    • Risk scoring with targeted friction; blind two-sided reviews

Why Airbnb Matters#

Why this is a different kind of hard problem
Every item is unique
  • No two homes are the same, so there's no catalogue to cache, no market price to copy, and each listing can be sold at most once a night
Supply changes faster than demand
  • Prices and calendars change 10,000 times a second, and some of those changes arrive hours late from websites you don't control
The platform holds other people's money
  • Billions are collected, held for weeks and paid to millions of hosts in dozens of currencies; the ledger, not the database, is the system of record

"Search fast on a copy, decide exactly on the source, and account for every unit of money you hold for someone else."

Self-Assessment Checklist#

After studying this case study, you should be able to:

Search and ranking:

Search and ranking0/3 ready
  • Filter millions of listings by map rectangle and dates at query time with geo cells and bit masks
  • Explain a retrieve, rank and re-rank funnel, and why retrieval uses daily vectors and an IVF index
  • Explain why map pins need different ranking from a list

Calendars and pricing:

Calendars and pricing0/3 ready
  • Design an iCal importer with source-tagged blocks, risk-based polling and refresh at reserve
  • Estimate the blind window of a polled calendar and how to shrink it
  • Turn a model's price suggestion into a safe, gradual price inside the host's range

Money and trust:

Money and trust0/3 ready
  • Model a marketplace's money as double-entry postings from charge to payout
  • Make charges and payouts idempotent across crashes and retries
  • Combine risk scoring, targeted friction and blind reviews

Sources#

Airbnb:

Airbnb engineering and research:

Reporting:

Standards:

Design note: the architecture, code, thresholds, polling schedules, dashboards and every number labelled as an assumption or estimate are an illustrative design for the problem, not a description of Airbnb's internal systems beyond what Airbnb has published. The search volume, average stay, iCal adoption and New Year's figures are assumptions.

Further Reading#

Related systems to study:

  • Read

    Google Maps (Bonus Problem 22 in this series): geographic partitioning, tiles and live data that changes underneath queries

  • Read

    Instagram (Bonus Problem 21): a retrieve-then-rank funnel for a personal feed

  • Read

    Razorpay (Bonus Problem 18): payment gateways, settlement and reconciliation from the provider's side

  • Read

    BookMyShow (Bonus Problem 16): seats that must never be sold twice, under a flash crowd

Papers and posts:

  • Paper

    Malay Haldar et al. — Learning To Rank Diversely at Airbnb (ranking a page as a set), 2022: https://arxiv.org/abs/2210.07774

  • Blog

    Airbnb Engineering — How Airbnb Achieved Metric Consistency at Scale (Minerva), 2021

Foundational concepts (this course):

  • Read

    Week 1, Day 1 — Partitioning Deep-Dive

  • Read

    Week 2, Day 2 — Idempotency in Practice

  • Read

    Week 2, Day 5 — Distributed Cron and Job Scheduling

  • Read

    Week 3, Day 5 — Audit Log System

  • Read

    Week 5, Day 4 — Conflict Resolution

  • Read

    Week 7, Day 1 — Search Fundamentals and Architecture

  • Read

    Week 7, Day 3 — Query Processing and Relevance

  • Read

    Week 8, Day 2 — Streaming vs Batch Processing

  • Read

    Week 9, Day 5 — Security Architecture

"Search fast on a copy, decide exactly on the source, and account for every unit of money you hold for someone else."

End of Side quest 24 · Airbnb Stays Marketplace
Clear the questFinish Airbnb.Stages clear as you read them. Marking complete unlocks after ~8 more min of reading.
  • 0/7stages cleared
design sheet · 7 diagrams · pdfRevise the whole design in ten minutesThe numbers, architecture, sequence, class and state diagrams, decisions and failure modes of this quest on a few printable pages.open the sheet →

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…