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.
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.
- 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.
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".
"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."
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
"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: Requirements Clarification (5 minutes)#
"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#
"How big is the supply?"
"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."
"How much is booked?"
"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."
"How often does a listing change?"
"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."
"Who else writes to a host's calendar?"
"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."
"And the money?"
"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#
- 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#
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: Back of the Envelope Estimation (5 minutes)#
"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."
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?
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#
| 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#
- 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#
- 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 |
Requirements and estimation
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.
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.
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: High-Level Design (10 minutes)#
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?
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.
"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#
%% 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 serviceKEY 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#
"You said you'd cover the reserve button once."
"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#
%% 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
High-level design
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.
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.
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: Deep Dives (20 minutes)#
Deep Dive 1: Geo Search With Dates at Query Time (Week 7 Concepts: Search Architecture; Week 1: Partitioning)#
"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.
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?
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.
# 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 rankerDeep Dive 2: Retrieve, Then Rank, Then Choose the Pins (Week 7 Concepts: Query Processing and Relevance)#
"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.
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?
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.
%% 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.
# 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, miniSearch and ranking
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.
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.
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.
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)#
"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.
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?
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.
%% 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.
# 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 indexerDeep Dive 4: Smart Pricing, a Suggestion With Guardrails (Week 8 Concepts: Streaming vs Batch)#
"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.
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?
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.
%% 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.
# 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 16000Deep Dive 5: Payments as a Marketplace: Hold, Ledger, Pay Out (Week 2 Concepts: Idempotency; Week 3: Audit Logs)#
"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.
₹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?
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.
%% 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_PENDINGuntil 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.
# 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)#
"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.
%% 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
VERIFYINGwhile 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.
# 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.
Calendars, pricing, money and trust
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.
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.
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.
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.
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.
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: Scaling and Edge Cases (5 minutes)#
"Walk me through the Diwali weekend from Meera's side, then the cases that bite."
Meera's Diwali Weekend#
- 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 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".
Scaling and edge cases
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.
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.
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: Monitoring and Operations#
"Five questions: can guests find homes, is what they find true, are calendars fresh, is the money right, and is trust holding?"
Monitoring Dashboard#
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#
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
Reading the dashboard
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.
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.
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: New Year's Week in North Goa#
The interviewer leans back. "One more."
"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."
- 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
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.
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.
Final challenge · Quest check
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.
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.
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.
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.
Interview Conclusion#
"Why not ask the calendar service for availability during search? It's the source of truth."
"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."
"And why not push to the other sites instead of this polling?"
"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."
"The single most important decision?"
"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#
- 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#
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:
- 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:
- 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:
- 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:
- Book
Airbnb — About us (9M+ active listings, 150K+ cities and towns, 220+ countries and regions, 2.5B+ guest arrivals, 5.5M+ hosts, $380B+ earned by hosts; last updated May 2026): https://news.airbnb.com/about-us/
- Read
Airbnb, Inc. — Form 10-K for the year ended 31 December 2025 (533 million nights and seats booked in 2025 and 492 million in 2024; GBV $91.3 billion; ~50 currencies; ~20 local payment methods; payment held until check-in and payout generally on the business day after the scheduled check-in; $6.891 billion of cash held on behalf of customers; system of trust including risk scoring, reviews and anti-party technology), filed 12 February 2026: https://www.sec.gov/Archives/edgar/data/1559720/000155972026000004/abnb-20251231.htm
- Read
Airbnb, Inc. — Form 10-K for the year ended 31 December 2024 (payout "generally occurs 24 hours after the scheduled check-in"): https://www.sec.gov/Archives/edgar/data/1559720/000155972025000010/abnb-20241231.htm
- Blog
Airbnb Help Center — Sync your Airbnb calendar with other websites (imported calendars update automatically every 3 hours; Refresh; .ics links): https://www.airbnb.com/help/article/99
- Blog
Airbnb Help Center — How Smart Pricing works ("hundreds of factors"; host minimum and maximum): https://www.airbnb.com/help/article/1168
Airbnb engineering and research:
- Paper
Mustafa Abdool et al. — Applying Embedding-Based Retrieval to Airbnb Search (root and leaf services; retrieval, first-stage and setwise models; ~100,000 available homes in popular locations; ~10,000 listing updates a second; ±3 days = 49 combinations; precomputed availability and pricing; IVF over HNSW; Euclidean distance; +0.31% conversion, ~16% less compute), arXiv, January 2026: https://arxiv.org/abs/2601.06873
- Blog
Airbnb Engineering — Embedding-Based Retrieval for Airbnb Search (two-tower model with the listing tower computed daily; trip-based negatives): https://airbnb.tech/ai-ml/embedding-based-retrieval-for-airbnb-search/
- Paper
Malay Haldar et al. — Learning to Rank for Maps at Airbnb (18 pins; 95% of searchers click ≤ 12 pins; bookability filter +1.9% uncancelled bookings over 34 million searchers per arm; mini-pins get 8× fewer clicks), KDD 2024: https://arxiv.org/abs/2407.00091
- Blog
Airbnb Engineering — Improving Search Ranking for Maps (re-centring the map: +0.27% uncancelled bookings, 1.5% fewer map moves): https://airbnb.tech/ai-ml/improving-search-ranking-for-maps/
- Paper
Mihajlo Grbovic and Haibin Cheng — Real-time Personalization using Embeddings for Search Ranking at Airbnb (search ranking and similar listings drive 99% of conversions; one listing accepts one guest for a set of dates), KDD 2018: https://www.kdd.org/kdd2018/accepted-papers/view/real-time-personalization-using-embeddings-for-search-ranking-at-airbnb
- Blog
Mihajlo Grbovic — Listing Embeddings in Search Ranking (32-dimensional embeddings for 4.5 million active listings from 800 million search click sessions), Airbnb Engineering, 2018: https://medium.com/airbnb-engineering/listing-embeddings-for-similar-listing-recommendations-and-real-time-personalization-in-search-601172f7603e
- Paper
Malay Haldar et al. — Applying Deep Learning to Airbnb Search (from gradient-boosted trees to neural networks), KDD 2019: https://arxiv.org/abs/1810.09591
- Paper
Peng Ye et al. — Customized Regression Model for Airbnb Dynamic Pricing (booking probability per listing-night, a price regression with a customised loss, personalization; powers Price Tips and Smart Pricing), KDD 2018: https://www.kdd.org/kdd2018/accepted-papers/view/customized-regression-model-for-airbnb-dynamic-pricing
- Blog
Airbnb Engineering — Pay as a Local (payments subdomains: Pay-in, Payout, Processing, Ledger, Settlement and Reconciliation; 20+ local payment methods in 14 months; redirect, async and direct flows; webhooks): https://airbnb.tech/payments/pay-as-a-local/
- Blog
Jon Chew — Avoiding Double Payments in a Distributed Payments System (Orpheus: pre-RPC, RPC and post-RPC phases; reads from the primary; retryable and non-retryable errors), Airbnb Engineering, 2019: https://medium.com/airbnb-engineering/avoiding-double-payments-in-a-distributed-payments-system-2981f6b070bb
Reporting:
- Blog
InfoQ — Airbnb's Machine Learning Approach to Financial Fraud (targeted friction: micro-authorisations, 3-D Secure, billing-statement verification; Airbnb absorbs chargebacks; from David Press's Airbnb Engineering post), 20 March 2018: https://www.infoq.com/news/2018/03/financial-fraud-ml-airbnb/
- Read
VentureBeat — Airbnb tweaks review system so guests don't fear retaliation from hosts (reviews hidden until both are submitted or 14 days pass), 2014: https://venturebeat.com/business/airbnb-tweaks-review-system-so-guests-dont-fear-retaliation-from-hosts/
Standards:
- Docs
RFC 5545 — Internet Calendaring and Scheduling Core Object Specification (iCalendar): https://www.rfc-editor.org/rfc/rfc5545
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."
- 0/7stages cleared
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.