Design Airbnb
Run this for someone else. You hold the answers; they do not. Read the prompt, keep the clock, and use the probes below when an answer is thin. Do not show them this page.
Open with this
Search 7 M listings by place, dates and price, book a stay without ever selling the same night twice, and keep calendars in sync with hosts’ other platforms. Take a couple of minutes on requirements, then we will do some numbers, then the design. I will interrupt to keep us moving.
The clock
- 4 min: functional requirements and scope
- 4 min: non-functional requirements, with numbers
- 5 min: back-of-envelope estimates
- 16 min: high-level design and one or two flows
- 16 min: deep dives and the close
Move them on out loud when a section overruns. The commonest failure is spending twenty minutes on requirements and never reaching a deep dive, and preventing that is your job as much as theirs.
Requirements · 8 min
Listen for: a scoped set of capabilities, an explicit out-of-scope list, and numeric targets rather than adjectives. Prompt with “what are you not building?” if they never scope, and “what number would make that requirement real?” if they say “fast” or “highly available”.
Functional (6)
- Search stays: By location (or map area), check-in and check-out dates, number of guests, price range and amenities. Only listings free for every night of the stay appear.
- View a listing: Photos, description, calendar, the total price for the chosen dates, and reviews.
- Book: Instant Book confirms immediately; Request to Book holds the dates for the host to accept within 24 hours. Payment is authorised at booking and captured later.
- Hosts manage calendars and prices: Block dates, set nightly prices and minimum stays, and connect calendars from other platforms (iCal).
- Cancel: Releases the nights and refunds according to the cancellation policy.
- Out of scope: Messaging between guests and hosts, reviews beyond a mention, payouts to hosts, and the pricing model itself.
Non-functional (6)
- No double bookings (zero): This is the requirement that drives the hardest trade-off: search can be seconds stale and still useful, but two guests must never both hold a confirmed booking for the same listing and night, even when they click at the same instant.
- Search latency (p99 < 500 ms): Geo, guests, price and availability across a date range, ranked.
- Scale (~1,200 searches/s avg · ~6 bookings/s avg): About 200 searches for every booking. Search is the volume; booking is the correctness.
- Freshness (calendar changes in search within a minute): A listing shown as available that turns out booked frustrates guests; the booking step catches it, but it should be rare.
- Payment correctness: A booking and its payment authorisation succeed or fail together; no confirmed stay without a payment, no charge without a stay.
- Availability (99.95 %): Search can degrade (fewer filters, cached results); the booking path must stay correct before it stays fast.
Estimates · 5 min
Ask for two or three numbers, not all of them. What matters is whether they state assumptions, round sensibly, and say what the number implies. Push once with “where did that come from?”
The numbers (6)
- Searches per second: ~1,200 avg · ~5,000 peak. 100 M searches a day ÷ 86 400 ≈ 1,160/s; travel planning peaks in evenings and after holidays, about 4× ≈ 5 k/s.
- Bookings per second: ~6 avg · ~50 peak. 500 k bookings a day ÷ 86 400 ≈ 6/s. Tiny: the booking path can afford a strongly consistent transaction on every request.
- Calendar rows: ~5 B. 7 M listings × 730 nights (two years ahead) ≈ 5 B listing-night rows in the source of truth, at ~50 B each ≈ 250 GB. Sharded by listing.
- Availability as bitmaps: ~320 MB. One bit per listing per night: 7 M × 365 bits ÷ 8 ≈ 320 MB for a year ahead. Small enough to sit inside every search shard’s memory, which is what makes date-range filtering fast.
- Candidate listings per search: ~5 k. A city search after geo and guest filters often matches 5 k listings; checking a 4-night range is 4 bit tests each, 20 k bit operations: microseconds.
- Hold expiry: ~15 min. A booking in progress holds its nights for 15 minutes while the guest pays; abandoned holds release automatically, so dates are never stuck.
High-level design · 16 min
Let them draw. Interrupt only to ask what backs a component or what a box actually does. Then pick one flow below and ask them to walk it end to end.
Components (14)
- Guest app: Searches, views listings and books. Sends an idempotency key with every booking attempt so a double tap or a retry never creates two bookings.
- Host app: Hosts edit listings, nightly prices, minimum stays and blocked dates, accept or decline requests, and connect external calendars.
- API gateway: Authentication, rate limiting (scrapers love listing data) and routing to search, booking and listing services.
- Search service: Builds the query (area, guests, amenities, price), filters candidates by availability for every night in the range, prices the stay, ranks, and returns a page.
- Search index (geo + attributes · sharded by region): One document per listing with location, capacity, amenities, base price and quality signals. Answers the non-date filters and returns a few thousand candidates.
- Availability bitmaps (bit per night, in memory): For each listing, a bitmap of free nights for the next year (and the minimum-stay rules), kept in memory next to each search shard. A date-range check is an AND over a few bits.
- Booking service: The correctness core: places holds on nights in one transaction, authorises payment, confirms or releases, and enforces the request-to-book state machine. Idempotent per guest key.
- Calendar store (Postgres · (listing, night) unique · sharded by listing): The source of truth: one row per booked or blocked listing-night with its status (held, booked, blocked) and owner. A unique constraint on (listing, night) makes double booking impossible.
- Payment provider (authorise · capture · refund): Authorises the guest’s card at booking and captures the charge later (often after check-in), so a declined host request or a cancellation is a voided authorisation, not a refund. See Design a Payment System.
- Listing service: Listing details, photos, house rules, nightly price overrides and minimum stays. Writes go to the listing store and reach search through the change stream.
- Listing store: Listings and their pricing rules. Read for listing pages and hydration; small compared with the calendar.
- Change stream (CDC → Kafka): Every committed change to calendars and listings, in order per listing. Feeds the availability bitmaps and the search index within seconds.
- Calendar sync (iCal import / export): Polls hosts’ calendars on other platforms every few minutes and blocks nights booked there; publishes our bookings as a feed they poll. The main source of double bookings across platforms.
- Pricing engine: Suggests nightly prices from demand, seasonality and local events; hosts who opt in have prices updated nightly. Its output is just another listing change.
Flows to ask them to walk (5)
- Search: Paris, 12 to 15 October, 2 guests: Non-date filters narrow 7 M listings to a few thousand; in-memory bitmaps keep only those free for all three nights; then pricing and ranking.
- The guest searches with an area, dates, guests and a price cap.
- The index returns listings in the area that fit two guests and match the filters.
- For each candidate, the search service checks the availability bitmap for the three nights and the minimum stay.
- Survivors are priced for the exact nights and ranked.
- The page is returned; the result may be seconds stale, which the booking step will catch.
- Book with Instant Book: The correctness path: hold the nights in one transaction against a unique constraint, authorise payment, confirm. Every step is idempotent.
- The guest confirms the stay; the request carries an idempotency key.
- The booking service inserts a held row for each night in one transaction.
- It authorises the payment for the total.
- On success the held rows become booked; on failure they are deleted.
- The change reaches search within seconds.
- Two guests click Book on the same nights at the same moment: The contention case. Search showed both of them the listing as free; the database decides, and exactly one wins.
- Both requests reach the booking service within milliseconds.
- Both transactions try to insert rows for 12, 13 and 14 October.
- The loser gets a clear "just booked" response and alternatives.
- Overlapping but different ranges are handled the same way.
- The host’s other platform sells the same nights: The failure path that actually causes double bookings: calendars synced by polling. The design narrows the window and handles the conflict when it happens.
- A guest books the listing on another platform; it appears in the host’s iCal feed there.
- Calendar sync polls the feed every few minutes and blocks the nights here.
- If one of the nights was booked here in the meantime, the insert fails: a real cross-platform conflict.
- Our own bookings are exported as a feed the other platform polls.
- A host changes prices and blocks dates: The background path: the listing and calendar stores are the truth; search follows them through the change stream.
- The host blocks a weekend and raises prices for a festival week.
- The pricing engine writes suggested nightly prices for hosts who opted in.
- Committed changes flow through CDC to the bitmaps and the index.
Deep dives · 16 min
Pick two. Ask the headline question, let them answer, then use the follow-ups. The follow-ups are where the level gets decided, so leave time for at least three of them.
Searching by dates
Ask: How do you find listings free for every night of a stay without checking billions of calendar rows?
Good answers name: Index for non-date filters, then in-memory availability bitmaps per listing, updated from CDC, Store available dates in the search document and filter in the engine, Join candidates against calendar rows in SQL, Precompute "available windows" per listing.
Our pick: The search index handles location, capacity, amenities and base price, returning a few thousand pre-scored candidates per query. Each search shard holds, in memory, a bitmap of free nights for the next 365 days for the listings it owns, plus minimum-stay and allowed check-in day rules, updated from the calendar change stream in milliseconds. The search service filters candidates with a bitwise check over the requested nights, then prices and ranks the survivors. Holds count as unavailable, so a listing being paid for vanishes from search. Search results may be seconds stale; the booking transaction is the only authority.
- A guest searches with flexible dates ("any weekend in November"). How?
Turn the request into a small set of concrete ranges (each Friday to Sunday) and test each listing’s bitmap against each range, keeping listings free for at least one. It is still a handful of bit operations per candidate, and the result can say which weekends are free. - What happens to bitmaps when a shard restarts?
They are rebuilt from a snapshot of the calendar for that shard’s listings, then the change stream is replayed from the snapshot’s position. The shard reports itself not ready until the replay catches up, so it never serves results from a half-built bitmap. - How stale can search be, and how would you measure it?
Normally a second or two: the CDC lag plus applying the change. Measure it directly by stamping changes with their commit time and tracking the delay to the bitmap update, and indirectly by the rate of bookings that fail with dates_unavailable after a search showed the listing free. - How do you rank once you have the available listings?
A two-stage ranker: a cheap score in the index (relevance, quality, price) to pick candidates, then a learned model on the survivors predicting the chance this guest books. Price relative to the area and the dates matters a lot, so it is computed per stay before ranking.
Never selling a night twice
Ask: Two guests try to book overlapping stays at the same instant. How do you guarantee only one succeeds?
Good answers name: One row per (listing, night) with a unique constraint; insert all nights in one transaction, Optimistic concurrency on a per-listing calendar version, Distributed lock per listing around check-then-write, Exclusion constraint on date ranges (Postgres GiST).
Our pick: The calendar has one row per occupied listing-night (held, booked or blocked), with a unique constraint on (listing_id, night) and a booking id. Booking inserts all nights of the stay in one transaction; a conflict on any night aborts the whole thing, so a guest never holds part of a stay. The calendar is sharded by listing id, so every booking for a listing is a single-shard transaction. Free nights have no rows; availability is the absence of a row. The idempotency key on the request maps to the booking id, so retries cannot create a second set of holds.
- Why insert rows for held nights before taking payment, rather than after?
Because payment is slow and can fail, and holding nothing while paying invites the race. Holding first, with an expiry, means the guest who pays is guaranteed the nights, and a guest who abandons checkout releases them automatically. - A host has the same flat listed twice (two listing ids). Can that double book?
Yes, because the constraint is per listing id. Model the physical unit: both listings point to one unit id, and the unique constraint is on (unit_id, night). Multi-unit hotels use the same idea with a count of rooms per night instead of a single row. - How does cancellation free the nights?
Delete the booking’s night rows (or mark them cancelled and exclude them from the constraint with a partial unique index), in the same transaction that changes the booking status. CDC then flips the bits back on, and the nights reappear in search within seconds. - What if the calendar shard for a listing is down?
That listing cannot be booked until it recovers or fails over to a replica, and the booking call fails fast with a retryable error. Other listings, on other shards, are unaffected. Correctness wins over availability on this path.
Holds, payments and request-to-book
Ask: Instant Book confirms at once; Request to Book waits up to 24 hours for the host. How do the nights and the money stay consistent?
Good answers name: Hold nights with an expiry, authorise (not capture) payment, confirm; a state machine and sweeper resolve every interruption, Charge immediately, refund on failure, Two-phase commit across calendar and payment provider.
Our pick: A booking moves through states: held → authorised → confirmed (Instant Book), or held → authorised → pending_host → confirmed or declined (Request to Book). The hold rows carry an expiry: 15 minutes for checkout, 24 hours for a host request. Payment is authorised with manual capture using the booking id as the idempotency key; capture happens around check-in, and declines, expiries and cancellations void the authorisation. A sweeper scans for holds past expiry and reconciles with the payment provider’s state before releasing or confirming, so a crash at any point resolves to a consistent outcome. Payments are covered in depth in Design a Payment System.
- The service crashes right after the payment was authorised but before confirming. What happens?
The held rows stay until expiry. The sweeper then asks the payment provider about the booking’s payment (by idempotency key), sees an authorisation, and confirms the booking. If the provider shows nothing, it releases the nights. Either way, the outcome matches the money. - A guest books a stay ten months away. The authorisation expires in seven days. Now what?
Take a small deposit or store the payment method and charge closer to the date, re-authorising before check-in, with the booking state tracking it. If the later charge fails, the guest is notified and the booking is cancelled after a grace period, releasing the nights. - Why does the hold count as unavailable in search?
So other guests do not see a listing they cannot book for the next 15 minutes. If the hold is released, the bits flip back. This trades a little availability for far fewer failed bookings.
Calendars on several platforms
Ask: Hosts list the same flat on three platforms, synced by iCal. How do you avoid double bookings you cannot see?
Good answers name: Frequent polling, blocks inserted against the same unique constraint, immediate conflict alerts; API integrations through channel managers where available, Poll rarely and trust hosts to keep calendars updated, Refuse external calendars.
Our pick: Calendar sync polls each connected feed every few minutes (more often for listings with bookings soon) and inserts "blocked" rows for nights booked elsewhere, through the same unique constraint. A failed insert means a real conflict: both platforms sold the night. The host is alerted at once with both bookings, and the policy (usually the platform that confirmed first keeps the guest, the other is rebooked with help) is applied. Our own calendar is exported as a feed. Professional hosts are encouraged onto channel managers that use APIs with near-real-time updates, which removes the polling window.
- How would you prioritise which feeds to poll most often?
By risk: listings with many upcoming free nights and a high booking rate, and feeds that changed recently. A listing booked out for months can be polled rarely; one with tomorrow free and high demand should be polled every minute or two. - How do you measure the damage?
Count conflicts detected by sync per thousand bookings, and the hours between a conflicting booking here and its detection. Both should fall as polling improves and as hosts move to API integrations. - Should an external block be able to cancel a booking made here?
Never automatically. The sync only inserts blocks for free nights; a conflict on an existing booking is surfaced to the host and support, because cancelling a guest's trip is a decision with policy, compensation and trust consequences. - How do you avoid importing your own bookings back from the other platform's feed?
Feeds can echo: the other platform imports our booking, then its export contains it. Each exported event carries our booking id in its UID; the importer recognises and skips events that originated here.
Nights, dates and time zones
Ask: What exactly is a "night", and why does it matter for correctness?
Good answers name: Store nights as plain local dates of the listing; check-out exclusive; convert only for display, Store stays as UTC timestamp ranges.
Our pick: A night is a DATE in the listing’s local calendar, and a stay is [check-in, check-out) with check-out exclusive. The calendar’s unique key is (listing_id, night DATE). Time zones only appear in rules about now: "same-day bookings until 18:00 local" and hold expiries use the listing’s zone. Prices are per night, so a stay’s price is the sum over its nights plus fees.
- A guest wants to book tonight at 23:30 local time. Allowed?
Depends on the host’s same-day cut-off, evaluated in the listing’s time zone, not the guest’s or the server’s. That is the main place time zones matter, and it is a rule check before the hold, not part of the conflict check. - How do minimum stays interact with availability search?
A free three-night gap in a listing with a four-night minimum is not bookable. The bitmap check also enforces minimum stay (and allowed check-in days) so such listings do not appear in search only to fail at booking. - What if a listing changes time zone (data fix)?
Nights are stored as local dates, so the calendar itself does not change. Only now-based rules (cut-offs, expiries) shift. Treat it as a listing edit that flows through CDC like any other. - How do you price a stay that crosses a price change?
Sum the price of each night individually, then apply length-of-stay discounts and fees. Because nights are explicit, a stay that starts in low season and ends in high season is priced correctly without special cases.
Close · 5 min
Ask what breaks first at ten times the load, and what they would build next. Then give them your read: one thing that was strong, one thing that was missing, one thing to practise. Be specific; “good job” helps nobody.