Airbnb System Design Interview: Search, Availability, and the Race to Book the Same Listing
Airbnb has reported more than 8 million active listings and over 5 million hosts, with cumulative guest arrivals passing 2 billion. The hard part is not the raw traffic. It is that a single home can accept exactly one guest for a given set of nights, so search is enormously read heavy while the booking write path has to stay strictly consistent under contention. Most scale numbers below are estimates derived from public figures unless marked as published.
Airbnb is a two sided marketplace where hosts list homes and guests search, book, and pay. A guest types a place and a date range, and the system filters millions of listings by location, dates, price, guest count, and amenities, then ranks them with personalization, all in a couple hundred milliseconds. The genuinely hard subsystem is the booking write path: two guests can try to reserve the same listing for overlapping dates at the same instant, and the design must let exactly one of them win. That means a strongly consistent reservation store, usually a relational database sharded by listing, with row level locking or optimistic concurrency on the calendar, plus short lived holds during checkout. Search runs off a separate denormalized index such as Elasticsearch that is updated asynchronously from booking and calendar events, so search is eventually consistent by design while reservations are not. Payments split a guest charge from a delayed host payout and hold funds until after check in, which forces careful idempotency so a retried request never charges twice. The whole thing is a study in choosing where to pay for strong consistency and where eventual consistency is fine.
Asked at: Asked at Airbnb, Booking.com, Uber, DoorDash, and most large product companies as the canonical booking and reservation problem. It also shows up framed as design a hotel reservation system or design an event ticketing system.
Why this question is asked
Interviewers like Airbnb because it forces a candidate to reason about two workloads with opposite requirements in the same system. Search is read heavy, latency sensitive, and tolerant of staleness, which pushes you toward denormalized indexes and caching. Booking is write heavy per listing, contended, and intolerant of any double sell, which pushes you toward strong consistency and locking. A strong candidate names that split early, then defends where they place transactions, how they prevent the overlapping date race, how listings flow from the source of truth into the search index, and how the payment split stays idempotent. It tests data modeling, concurrency control, and event driven propagation all at once.
Requirements
Always clarify these in the first 5 minutes of the interview. Do not start drawing boxes until both lists are agreed.
Functional requirements
- Guests search listings by location or map bounding box, date range, guest count, price, and amenity filters
- Rank and personalize results using listing quality, relevance, and the guest's recent session behavior
- Hosts create and edit listings, set nightly prices and rules, and manage a calendar of available dates
- Show accurate per night availability and total price including cleaning fees, service fees, and taxes
- Let a guest place a reservation for a listing and date range without ever double booking
- Charge the guest, hold the money, and pay the host out after check in, across currencies
- Support messaging between host and guest and two sided reviews after a completed stay
- Send booking confirmations, reminders, and cancellation or refund flows
- Let hosts and guests cancel subject to the listing's cancellation policy
Non-functional requirements
- Search results in roughly 200 ms at the 99th percentile for interactive feel
- Zero double bookings, the booking write path must be strongly consistent
- High availability for search and browsing, degrade gracefully if ranking is slow
- Search index may lag the source of truth by seconds, that staleness is acceptable
- Payments must be exactly once in effect, a retry can never charge a guest twice
- Horizontally scalable reads, the read to write ratio is very lopsided
- Global low latency, guests and listings span many regions and currencies
- Durable audit trail for money movement and reservation state changes
Back-of-envelope scale estimates
Show your math. Pulling numbers from thin air signals you have not thought about the load.
Active listings
8M+ (published)
Airbnb has publicly reported more than 8 million active listings. Even at a few kilobytes of core metadata each, the primary listing set is only tens of gigabytes, so the challenge is query patterns and the calendar, not raw listing size.
Search QPS
~15K-30K peak (Derived estimate)
Assume roughly 150M monthly active searchers doing a handful of searches per active session, concentrated in evenings and weekends. Spreading a few hundred million searches per day over a day with a 5x to 10x peak factor lands in the low tens of thousands of queries per second. Derived, not published.
Bookings per day
~1M-2M (Derived estimate)
Cumulative guest arrivals have passed 2 billion over Airbnb's lifetime. Recent annual nights and experiences booked are in the hundreds of millions, so on the order of one to two million confirmed bookings per day. This makes write TPS modest, low tens per second on average, but heavily skewed to popular listings and dates.
Read to write ratio
~1000:1 or higher (Derived estimate)
Tens of thousands of searches per second against low tens of bookings per second gives a ratio well past a thousand to one. This asymmetry is the reason search and booking are split into separate systems with separate stores.
Availability rows
Billions (Derived estimate)
8M listings times a booking window of roughly 365 to 500 future days, if you model availability one row per listing per day, is on the order of 3 to 4 billion calendar rows. A range based representation shrinks this a lot, which is one reason range models are attractive.
Payment records
Hundreds of millions per year (Derived estimate)
Each booking generates at least a guest charge and a host payout, plus refunds and adjustments, so a year of bookings produces several hundred million money movement events that all need idempotency keys and an audit trail.
High-level architecture
A guest request lands at an edge and API gateway that fans out to backend services, an approach Airbnb moved to as it broke its Rails monolith into service oriented architecture. Browsing and search hit a read path: the search service queries a denormalized listing index, typically Elasticsearch, that supports geospatial filtering by bounding box or radius plus structured filters on price, guests, and amenities. Candidate listings are then scored by a ranking service that layers in personalization from the guest's recent session, and results, minus anything already booked for the requested dates, come back within a couple hundred milliseconds. When the guest picks a listing and dates, they cross into the write path. The reservation service checks the availability calendar in a strongly consistent relational store, places a short lived hold on those nights so the guest can finish checkout, and confirms the reservation inside a transaction that flips the calendar to booked. The payment service then charges the guest through a payment orchestration layer, records the money movement idempotently, and schedules the host payout for after check in. Every state change that matters to search, a new listing, an edited price, a calendar update, or a completed booking, is published as an event onto a bus such as Kafka. An indexing pipeline consumes those events and updates Elasticsearch asynchronously, which is why search is eventually consistent while the reservation store is the source of truth.
In a real interview, sketch this on the whiteboard before diving into any single box.
Core components
Walk through each service. The interviewer wants to hear what each one owns, not just the names.
Search and geo service
Serves the read heavy query path against a denormalized index like Elasticsearch that combines geo queries with structured filters. It resolves a map bounding box or a place plus radius, applies date, price, guest, and amenity filters, and returns a candidate set fast. It is built to scale out by adding nodes and replicas independently of the booking system.
Ranking and personalization service
Takes the candidate listings from search and orders them using listing quality signals and real time personalization. Airbnb has published work on listing and user embeddings that push listings similar to ones the guest engaged with this session up, and ones they skipped down. This is where machine learning models and feature stores live.
Listing and calendar service
The host facing source of truth for listing content, pricing rules, and per date availability. Hosts edit listings and block or open dates here. Every mutation is written transactionally and emitted as an event so the search index and pricing can catch up.
Reservation and booking service
Owns the strongly consistent write path. It validates that the requested nights are free, places a temporary hold during checkout, and commits the booking in a transaction that also marks the calendar. This is where double booking is prevented with locking or optimistic concurrency.
Pricing service
Computes the total a guest sees: nightly price for each date, any host set weekend or seasonal adjustments or smart pricing suggestions, cleaning and service fees, taxes, and currency conversion. It reads calendar and rules and returns a priced quote that the booking flow reuses.
Payments and payout orchestration
Splits the guest charge from the host payout and holds funds until after check in, acting as an escrow style intermediary. It integrates external processors and uses an idempotency framework so retries never double charge. Airbnb built Orpheus for exactly this.
Indexing and event pipeline
Consumes listing, calendar, and booking events from Kafka and updates the Elasticsearch index and downstream caches. This decouples the write systems from search so each scales on its own, at the cost of a few seconds of index staleness.
Data model
Pick the right store per table. Justify each choice with the access pattern, not by reflex.
listingslisting_idhost_idgeo_latgeo_lngattributes_jsonbase_priceSource of truth for listing content and location, sharded by listing_id or host_id. Attributes such as amenities and property type live in a flexible column and are flattened into the search index.
availabilitylisting_idstart_dateend_datestatusmin_nightsversionCalendar state, either one row per night or a range model with status of open, held, or booked. Range models cut row counts sharply. The version column supports optimistic concurrency to prevent overlapping writes.
reservationsreservation_idlisting_idguest_idcheck_incheck_outstatusOne row per booking with status moving through pending, held, confirmed, and cancelled. Shares a shard with the listing so the availability check and the reservation insert can commit in one transaction.
pricing_ruleslisting_idrule_typedate_rangeamountcurrencyHost pricing including base nightly rate, weekend and seasonal overrides, discounts, and fees. Read by the pricing service to build a quote. Currency stored so conversion happens at quote time.
paymentspayment_idreservation_ididempotency_keydirectionamountstateMoney movement records for guest charges and host payouts. Direction distinguishes charge from payout. The idempotency_key is unique and used as the row lock and shard key so retries collapse onto one record.
reviewsreview_idreservation_idauthor_idtarget_typeratingbodyTwo sided reviews tied to a completed reservation. Target_type marks whether the guest is reviewing the listing or the host is reviewing the guest. Only released after both sides submit or a window closes.
Deep dives
These are the conversations the interviewer is steering you toward. Practice each one until you can talk through it without notes.
Preventing the double booking race
Two guests can hit confirm on the same listing for overlapping nights within milliseconds of each other. The read heavy search path can be eventually consistent, but this write must be linearizable for the affected listing. The clean approach is to make the availability check and the reservation commit a single transaction in a relational store, keyed by listing so both rows sit on the same shard. Optimistic concurrency works well here: read the calendar with a version number, and on commit require that version to be unchanged, so the second writer's transaction fails and retries or gets rejected. Pessimistic row locks on the affected date range are the alternative and are simpler to reason about but hold locks longer. Either way the invariant is that no two confirmed reservations for one listing overlap in dates, and that check lives inside the transaction, never in application code that read stale data a moment earlier.
Holds during checkout
A guest who selects dates needs time to enter payment details, and you cannot leave those nights bookable by someone else during that window, nor can you permanently commit before payment succeeds. The standard pattern is a temporary hold: mark the date range as held with a short expiry, a handful of minutes, so a concurrent guest sees it as unavailable. If the guest completes payment the hold converts to a confirmed reservation. If they abandon or the timer expires, a sweeper or a lazy check on the next read releases it. Holds are effectively soft locks with a TTL. The tricky parts are making expiry reliable without a single point of failure, and making the convert to confirmed step idempotent so a retried confirmation does not create a second reservation.
Geospatial search with filters
A guest searches by a place or by panning a map, which becomes a bounding box or a center plus radius, combined with structured filters on dates, price, guest count, and amenities. Elasticsearch is a common fit because it does geo queries and structured filtering in one index, and it scales reads by sharding and replicating. Date availability is the awkward filter: you can denormalize a compact availability summary into each listing document and refresh it from calendar events, or filter candidates in Elasticsearch and then verify true availability against the reservation store for the final page. The index is denormalized and rebuilt from events, so it is designed to be a few seconds stale. That is acceptable because the reservation store, not the index, is the authority that actually blocks a double booking at commit time.
Ranking and real time personalization
Geo and filter matching produces far too many candidates, so relevance ranking decides what a guest actually sees. Airbnb has published work using listing and user embeddings to personalize ranking within a session in real time. They collect, using Kafka, the set of listings a guest clicked and the set they skipped over a recent window, and shift results toward listings similar to the liked ones and away from the skipped ones. On top of session signals sit model features for listing quality, price competitiveness, booking probability, and host reliability. Because this is a two sided marketplace where a listing serves one guest per date range and users rarely book the same item twice, the ranking objective has to balance guest fit against host and marketplace outcomes, which makes it a harder problem than typical content recommendation.
Propagating listings into the search index
The listing and calendar service is the source of truth, but search reads from Elasticsearch, so changes have to flow across. The robust pattern is event driven: every create, edit, price change, calendar update, and completed booking emits an event onto Kafka, and an indexing pipeline consumes those and updates the index. Change data capture straight off the database binlog is a common way to generate those events without dual writes. This decouples the systems so search scales independently and a search outage never blocks bookings. The cost is eventual consistency, a listing edit or a freshly booked date can take seconds to reflect in results. That is fine for browsing, and the final booking step re checks the authoritative calendar, so a stale index never causes an oversell.
Idempotent split payments and payouts
A booking charges the guest now but pays the host later, after check in, so the money system holds funds in between like an escrow. Networks fail and clients retry, and the nightmare is a retry that charges a guest twice. Airbnb built an idempotency framework called Orpheus for this. Every payment request carries an idempotency key, and the request is split into a pre RPC phase that records intent in the database, an RPC phase that calls the external processor with no database work, and a post RPC phase that records the result. The request acquires a database row level lock on the idempotency key and holds a lease until the processor response is written back, and all idempotency reads and writes go to the master to avoid replica lag executing a payment twice. Airbnb reported reaching five nines of payment consistency while payment volume doubled, and they sharded the idempotency database by key since keys have high cardinality and even distribution.
Consistency boundaries across the system
The whole design is an exercise in placing strong consistency only where it earns its cost. The reservation and payment write paths are strongly consistent, transactional, and single sharded per listing or per idempotency key. Everything else, search results, ranking features, index freshness, and analytics, is eventually consistent and driven off asynchronous events. Multi step flows that cross services, such as book then charge then confirm then schedule payout, are coordinated as a saga with compensating actions rather than a distributed two phase commit, because a global lock across payments and reservations would kill availability. If payment fails after a hold, the compensation releases the hold. This boundary drawing, strong where a guest could be double charged or double booked and eventual everywhere else, is the core insight interviewers are listening for.
Trade-offs to discuss
Every senior interviewer expects you to surface at least 3 of these. Pick the decisions, state the alternatives, and justify your choice.
Separate search index versus querying the booking database directly
A denormalized Elasticsearch index gives fast geo and filter queries and scales reads on its own, at the price of eventual consistency. Querying the transactional store directly would be always fresh but would not survive the read volume and would couple search availability to booking. The split wins because the final booking step re verifies against the source of truth anyway.
Optimistic concurrency versus pessimistic locks on the calendar
Optimistic version checks avoid holding locks and perform well when true collisions on a specific listing and date are rare, which they usually are. Pessimistic locks are simpler to reason about and better under high contention on a single hot listing. Many designs use optimistic by default and accept the occasional retry.
Range based availability versus one row per night
Range rows keep the calendar compact, billions of nightly rows collapse into far fewer, and are cheap to store. Per night rows are trivial to query and update for a single date but explode in count. Ranges save space and IO but need careful splitting and merging logic when a booking carves a hole in an open range.
Holds with TTL versus committing on payment success only
A hold reserves the dates while the guest pays, which prevents a frustrating race where two people both reach the payment screen. It adds expiry and cleanup complexity and can briefly block a listing that is then abandoned. Committing only after payment is simpler but loses more concurrent guests to lost races. Holds are the better guest experience.
Saga with compensation versus two phase commit across services
A saga keeps each service autonomous and available, coordinating book, charge, and payout with compensating actions if a later step fails. Two phase commit would give cleaner atomicity but needs a coordinator, holds locks across service and payment boundaries, and hurts availability. For a booking flow that touches an external payment processor, the saga is the pragmatic choice.
Strong consistency only on the write path versus everywhere
Reserving transactions and master reads for booking and payments keeps the pieces that could double book or double charge correct, while letting search, ranking, and analytics run on cheap eventually consistent reads. Making everything strongly consistent would be simpler mentally but would not scale the read path and would waste money on guarantees browsing does not need.
How Airbnb actually does it
Airbnb started as a Ruby on Rails monolith and moved to service oriented architecture as it grew, adopting SmartStack for service discovery and running MySQL on RDS with proxies for connection pooling and throttling. The team has written about partitioning its main database to scale writes. On search, Airbnb published a KDD 2018 paper on real time personalization using listing and user embeddings, collecting clicked and skipped listings via Kafka to personalize ranking within a session, and later work on embedding based retrieval. On money, Airbnb built the Orpheus idempotency framework to avoid double payments in its distributed payment system, splitting each request into pre RPC, RPC, and post RPC phases with a row level lock on the idempotency key, and reported reaching five nines of payment consistency as volume doubled. They later wrote about rebuilding payment orchestration. Airbnb also created Apache Airflow to schedule the batch and data pipelines behind pricing and analytics.
Lessons to study before this interview
If any of these topics are fuzzy, the interviewer will catch it. Each lesson is 15 to 60 minutes with diagrams, code, and a quiz.