System design interview guide
Airbnb Reservation System Design
A guest tries to book a beach house for a holiday weekend while another guest is checking out and a host calendar import is running in the background. The system must let many people browse quickly, but only one reservation may own each night when checkout commits.

Problem statement
Design the reservation core for a short-term rental marketplace: guests browse dates, place a temporary hold, pay, confirm, cancel, and receive refunds while hosts keep calendars in sync. The hard part is that browsing can be slightly stale, but checkout must never double-book overlapping nights.
Introduction
Two guests open the same cabin for the same long weekend. Both see the dates as available. Both enter their card details. One guest should win the nights; the other should get a clear message that the dates disappeared. If both receive a confirmation, the host has a real-world problem, not only a database bug.
An Airbnb-style reservation system is a marketplace system with one unforgiving rule: one listing-night can belong to only one confirmed reservation. Everything else sits around that rule. Search can be fast and slightly stale. Pricing can be computed by a separate service. Emails and calendar exports can happen later. But the checkout path must be exact because it changes what the host can sell.
The interview is usually not about building all of Airbnb. It is about the reservation core: check availability, create a short hold, authorize payment, confirm, cancel, refund, and sync calendars. A strong design explains the order of those steps and why the order matters. If you capture money before you own the nights, you may need refunds for guests who never had a booking. If you hold nights too long, hosts lose revenue. If you trust a cached availability result, two guests can race through checkout.
How to approach
Start with one booking journey instead of drawing every marketplace feature. The interviewer wants to hear how you keep the calendar correct under pressure, and the easiest way to show that is to walk one guest from search to confirmation while another guest races for the same nights.
Clarifying Q&A
You: "Are we designing the whole marketplace, or only the reservation subsystem for listings that already exist?"
Interviewer: "Only the reservation core: availability, booking, payment, cancellation, and calendar sync."
That answer keeps reviews, messaging, host onboarding, and search ranking out of the main design so you can spend time on inventory correctness.
You: "Is instant book required, or does the host approve each request before the reservation is confirmed?"
Interviewer: "Assume instant book for the main path. You can mention request-to-book as a variant."
Now the system must lock inventory during checkout. Host approval is not the thing preventing two guests from taking the same dates.
You: "Do guests pay immediately, or do we place a temporary hold while payment is authorized?"
Interviewer: "Use hold and confirm. The guest should have a short checkout window."
That tells you the reservation state machine needs held, confirmed, expired, and cancelled, and the system needs a sweeper for stale holds.
You: "Can availability search be slightly stale if checkout re-checks the live calendar?"
Interviewer: "Yes. Browsing can lag, but final booking must be correct."
This is the key tradeoff. Search can use cache or an index, but checkout must read the authoritative calendar.
You: "Should we include pricing, taxes, and refunds, or leave them as external services?"
Interviewer: "Include enough to show quote locking and idempotent payment/refund calls."
Now you should name the quote record, payment idempotency key, and refund policy, without designing a full finance platform.
Minute pacing + In the room
| Minutes | Focus |
|---|---|
| 0-5 | Clarify scope, instant book, hold duration, stale search, and payment boundary. |
| 5-12 | Capacity, HLD, and the difference between browsing availability and booking. |
| 12-25 | Detailed design for hold, confirm, cancel, and host calendar sync. |
| 25-35 | Architectural dig on two guests racing for one night, plus payment idempotency. |
| 35-45 | Failure, observability, fraud, costs, bottlenecks, and interview pushes. |
In the room: "I will keep the reservation calendar as the source of truth. Search can read from a fast index and may be a little stale, but reserve and confirm run against the live calendar in one short transaction. I will create a hold with an expiry, authorize payment with an idempotency key, capture only after the reservation is confirmed, and send emails and calendar exports after the database commit. Then I will cover cancellation, refund, and what happens when two guests race for the same night."
Out of scope
Full marketplace search and ranking. Search ranking is a large product by itself. For this interview, it is enough to say that search consumes availability updates and may be slightly stale.
Host onboarding, identity checks, and reviews. These matter to the business, but they do not change the central calendar invariant. Keep the round focused on reservation safety.
Building a payment processor. Use a payment provider boundary. The design should explain authorization, capture, refund, and idempotency, not card network internals.
Legal dispute handling. Chargebacks and guest-host disputes need workflows, but they are not part of the normal booking path unless the interviewer expands the scope.
Capacity estimation
Use rough numbers to decide which path needs strict correctness and which path needs scale.
| Dimension | Example scale | What it means |
|---|---|---|
| Listings | Millions worldwide | Partition calendar data by listing_id or region so one listing does not touch every shard. |
| Availability reads | Very high during browsing | Serve from cache or search index, then re-check on reserve. |
| Booking writes | Much lower than reads | Keep each write transaction short and exact. |
| Hot dates | Holiday weekends and viral homes | Expect many guests to race for the same listing-night. |
| Host calendar sync | Delayed external feeds | Treat imports as conflict signals, not as stronger truth than confirmed reservations. |
| Payments | Slow external dependency | Do not hold database locks while waiting for the payment provider. |
The main implication is simple: the read path and the write path have different contracts. Browsing answers, "what seems available right now?" Booking answers, "did I take these nights?" If you make every browse read strongly consistent, the product feels slow and expensive. If you let checkout trust a stale browse result, the product double-books.
High-level architecture
A good high-level design separates the parts that answer quickly from the parts that own money and inventory. The guest-facing API coordinates the journey, but the calendar service owns the rule that one listing-night cannot be sold twice.
The calendar service stores authoritative night inventory for each listing. It knows whether a night is available, held, booked, blocked by host, or imported as unavailable from an external calendar. The booking service runs the state machine from hold to confirmed to cancelled. The pricing service returns a quote with rates, taxes, fees, and a quote expiry. The payment adapter talks to a provider and makes authorization, capture, and refund calls safe to retry. The outbox and workers update search, send notifications, and export calendar changes after the booking transaction commits.
[ Guest app ]
|
v
[ Reservation API ] ---- quote ----> [ Pricing / tax service ]
|
| reserve / confirm / cancel
v
[ Booking service ] ---- authoritative writes ----> [ Calendar DB ]
| (listing-night inventory)
|
+---- authorize / capture / refund ----> [ Payment provider ]
|
+---- outbox after commit ----> [ Email ] [ Search index ] [ Host calendar export ]
[ Host calendar import ] ---- reconcile ----> [ Calendar service ]
In the room: Draw the synchronous path first: guest -> reservation API -> booking service -> calendar database. Then draw payment as a provider call around the hold and confirm states, and draw email/search/calendar sync as asynchronous work after commit. This order tells the interviewer you know which parts are allowed to lag.
Core design approaches
There are three common ways to model reservation inventory. Pick one and explain why.
| Approach | Plain idea | Good when | Watch out |
|---|---|---|---|
| One row per listing-night | Store each night as available, held, booked, or blocked | Most short-term rental interviews | Date ranges become many row locks, but the model is easy to reason about. |
| Range reservation with exclusion rule | Store date intervals and let the database reject overlap | Strong when the database supports range constraints | Harder to shard and harder to explain quickly. |
| Count per listing-night | Store remaining_units for hotel-like multi-unit inventory | Hotels, room types, or shared inventory pools | Must prevent the count from going below zero. |
For an Airbnb-style single home, one row per listing-night is usually the clearest interview answer. A request for July 4 to July 7 claims the nights July 4, July 5, and July 6. The checkout transaction locks those rows, verifies none are held or booked, and then writes the hold or booking. For a room type with 30 identical rooms, the same table can store remaining_units instead of a single booked bit.
For the product flow, use hold then confirm. A hold gives the guest a few minutes to pay without letting another guest take the same nights. It also creates a clock you must operate. If payment never completes, the hold expires and the nights return to availability. If payment succeeds, the reservation moves to confirmed and the hold no longer blocks as a separate thing.
Data model
The data model should make the reservation invariant obvious. If you cannot point to the row that prevents overlap, the design is not finished.
| Record | Important fields | Why it exists |
|---|---|---|
listing | listing_id, host, timezone, status | Dates are interpreted in listing-local time, not the guest's device timezone. |
listing_night | listing_id, night_date, state, reservation_id, held_until, source | Authoritative inventory row for each sellable night. |
reservation | reservation_id, guest, listing, check-in, check-out, status, quote id | User-visible booking object and state machine. |
quote | nightly rates, fees, taxes, currency, expires_at, version | Freezes the price the guest accepted during the hold window. |
payment_intent | provider id, idempotency key, auth status, capture status | Connects money movement to a reservation without double charge. |
refund | refund id, amount, reason, provider id, idempotency key | Makes cancellation and provider retries auditable. |
calendar_sync_event | source, external uid, range, status, conflict reason | Tracks imports and exports for host calendars. |
outbox_event | event type, payload id, created_at, processed_at | Moves email, search, and calendar side effects after commit. |
Use half-open date ranges: [check_in, check_out). A stay from July 4 to July 7 owns July 4, July 5, and July 6 nights, and checkout happens on July 7. Saying this out loud avoids timezone and off-by-one mistakes.
Detailed design
The reservation journey has four user-visible moments: the guest browses dates, places a hold, pays to confirm, and may later cancel. Each moment should leave the system in a state support can explain. The order matters because inventory and money are both involved, and neither should pretend the other succeeded before it actually did.
When a guest checks availability, the API can read from a fast availability index or a short-lived cache. That read is for browsing only. It helps the guest choose dates, but it does not create a right to those dates. The response should avoid language like "guaranteed available" because another guest may be holding or booking the same nights a moment later. If pricing is requested, the API asks the pricing service for a quote and returns a quote id with an expiry. The quote stores nightly rates, cleaning fees, taxes, service fees, and currency so the amount does not drift while the guest is filling payment details.
When the guest taps reserve or continue to payment, the booking service starts a short transaction on the authoritative calendar. It loads the quote and verifies it has not expired. It locks the listing_night rows for every night in the half-open range. If any night is already held by another active hold, booked by a confirmed reservation, or blocked by the host, the service rolls back and returns a 409 conflict with copy such as "These dates were just taken." If all nights are free, the service writes a reservation row in HELD, marks the nights held for that reservation, sets held_until, stores the idempotency key for the reserve request, commits, and returns a payment client secret.
Only after the hold exists should the payment step run. The payment adapter authorizes the card with an idempotency key based on the reservation id and payment attempt. If the network times out after the provider accepted the authorization, a retry must ask for the same authorization result, not create a second hold on the guest's card. The API should not keep database locks open while waiting for the provider. The hold is already the inventory protection.
Confirmation is another short database step. The booking service verifies the reservation is still in HELD, the hold has not expired, and the payment authorization is valid. It captures the payment or records a provider capture result using the same idempotent payment intent. Then it moves the reservation to CONFIRMED, marks the nights booked, and writes outbox events for guest email, host email, search-index update, and calendar export. If capture fails in a clear terminal way, the system releases the hold and tells the guest payment failed. If the provider result is unclear, the reservation may sit in a short PAYMENT_PENDING state while reconciliation asks the provider what happened.
Cancellation follows the same discipline. The booking service checks the cancellation policy for the reservation, computes the refund amount, and moves the reservation to CANCELLED in a transaction that releases the nights back to the calendar. It writes a refund command with an idempotency key and sends it to the payment provider after commit. That order matters: the app should not show the nights as free while the reservation still says confirmed, and provider retries should not create multiple refunds.
Host calendar sync is slower and less trusted than the booking path. Imports from iCal or channel managers create external blocks or conflict records. If an imported block overlaps an already confirmed reservation from this system, do not delete the reservation. Keep the confirmed booking, mark the sync event as conflict, alert the host, and stop selling future overlapping dates until the host resolves the mismatch. Exports to external calendars happen through the outbox after booking or cancellation commits.
In the room recap: "Availability browse is a hint. Reserve locks the live listing-night rows and creates a short hold. Payment authorization is idempotent and does not keep database locks open. Confirm captures payment, marks nights booked, and emits outbox events after commit. Cancel releases nights and sends an idempotent refund. Calendar sync can lag, but it cannot silently override confirmed reservations."
Architectural dig
Booking the last holiday night
The main race is two guests trying to reserve the same listing-night at almost the same time. A weak design performs GET availability, sees the night as free, and then inserts a reservation later. Between those two actions, another request can do the same thing. Both users passed the check because the check was not the lock.
Bad design
Guest A: read July 4 free
Guest B: read July 4 free
Guest A: insert reservation and charge
Guest B: insert reservation and charge
Result: two guests, one home
The safer design makes the write path own the truth. The reserve transaction locks all nights in the requested range and changes them together. If the database uses one row per night, the transaction locks those rows. If it uses a reservation-night table, a unique rule on (listing_id, night_date) for active reservations rejects the second writer. Either way, the second request receives a 409 before money is captured.
Great design
BEGIN
load quote and check expiry
lock listing_night rows for [check_in, check_out)
if any active hold/book/block exists -> ROLLBACK + 409
create HELD reservation
mark nights held with held_until
COMMIT
then authorize payment with idempotency key

The next subtle race is payment. If the guest double-taps or the mobile network retries, the same booking intent must return the same reservation and payment result. Store the idempotency key on the reservation request, and send a separate idempotency key to the payment provider for authorization, capture, and refund. Payment idempotency alone is not enough; inventory must also dedupe the same logical booking attempt.
The host calendar sync path has its own conflict story. External calendars are delayed and often lossy. Treat imports as data to reconcile, not commands that can overwrite a confirmed reservation. Confirmed bookings from the reservation database win. Imported conflicts create visible host warnings and may block future open nights, but they do not erase guest trust.
Key challenges
| Challenge | What goes wrong if ignored | Strong answer |
|---|---|---|
| Availability vs booking | Guest sees available and assumes it is reserved | Say browsing can be stale; checkout re-checks and locks. |
| Overlapping date ranges | Two reservations share a night | Lock night rows or use a database rule that rejects overlap. |
| Hold expiry | Calendar stays blocked after abandoned checkout | Sweeper releases expired holds and records why. |
| Payment retry | Double charge or charged-without-booking state | Idempotency keys and reconciliation by reservation id. |
| Pricing changes | Guest accepts one price but pays another | Store a quote id and quote version during hold. |
| Calendar sync lag | Host calendar disagrees with guest confirmation | Booking database is source of truth; imports create conflict records. |
| Fraud review | Risk checks hold inventory forever | Short risk window, explicit pending state, and release on timeout. |
Scaling the system
Shard or partition calendar data by listing_id because booking conflicts are naturally local to one listing. A viral property still creates a hot shard, but it is better for one listing to be hot than for every popular listing to fight over one global table. Keep transactions short: lock the date rows, update state, commit, and move side effects to the outbox.
Serve browsing from read copies, materialized availability indexes, or cache. Invalidate or update those views after booking and cancellation commits, but accept that they may lag for a short time. Measure the lag with a metric such as conflict_after_search_available: how often a guest saw available in search and then lost at reserve. That number tells you whether cache freshness is a product problem.
For multi-region marketplaces, avoid pretending one global active-active calendar is simple. Most listings have a home region. Route booking writes for that listing to its home region or a single primary owner. Read search data globally if needed, but do not let two regions confirm the same listing-night independently.
Failure handling
| Failure | User or host experience | What to build |
|---|---|---|
| Payment provider timeout | Guest sees processing, bank may show pending authorization | Reconcile by payment intent; either confirm, void, or release hold. |
| Database failover during reserve | Guest may retry after timeout | Idempotency key returns same reservation if commit happened. |
| Worker crash after confirm | Booking exists but no email or search update yet | Outbox retries side effects until processed. |
| Hold sweeper delayed | Dates look unavailable longer than promised | Alert on expired holds still blocking nights. |
| Calendar import conflict | Host says another platform already booked it | Conflict record, host alert, and manual resolution without deleting confirmed guest booking. |
| Pricing service unavailable | Guest cannot get final checkout amount | Fail closed for checkout; do not invent taxes or fees. |
A useful rule: if the system is unsure whether inventory is free, do not confirm. If it is unsure whether a payment provider captured money, reconcile before charging again. Guessing creates the worst combination: angry guests, angry hosts, and messy refunds.
API design
| Endpoint | Role |
|---|---|
GET /v1/listings/{listing_id}/availability?from=&to= | Browse availability for listing-local dates. |
POST /v1/listings/{listing_id}/quotes | Compute and freeze price, taxes, and fees for a short time. |
POST /v1/reservations | Create a hold for a date range with an idempotency key. |
POST /v1/reservations/{reservation_id}:confirm | Confirm after payment authorization or capture. |
POST /v1/reservations/{reservation_id}:cancel | Cancel and start refund flow. |
POST /v1/calendar-imports/{source}:sync | Import host blocks and detect conflicts. |
POST /v1/reservations should include listing_id, check_in, check_out, guest_count, quote_id, and idempotency_key. The dates are listing-local dates, and the range is half-open. The server, not the client, expands that range into nights and locks them.
GET availability -> may read cache or index
POST quote -> freezes price for a short window
POST reservation -> live calendar transaction creates HELD
POST confirm -> payment capture + CONFIRMED + outbox
Important errors are 409 for dates no longer available, 410 for hold expired, 402 for payment failure, 422 for quote expired or policy mismatch, and 429 for abuse or bot protection.
Observability
Watch the journey, not only the API success rate.
| Signal | Why it matters |
|---|---|
reserve.conflict_rate by listing and date | Separates healthy contention from stale search or bugs. |
| Expired holds still blocking nights | Finds abandoned inventory and broken sweepers. |
| Payment auth-to-confirm lag | Finds money in limbo before support does. |
| Quote mismatch and expired quote rate | Shows whether pricing TTL is too short or pricing deploys are unstable. |
| Calendar sync conflicts by source | Identifies unreliable channel managers or host setup issues. |
| Outbox age for search and email | Confirms side effects are not stuck after booking. |
| Refund retry and duplicate refund attempts | Protects finance correctness. |
Dashboards should answer a support question: "Did this guest own the nights, did we charge them, and did we tell the host?" If the answer needs five logs and guesswork, the design is missing traceability.
Security and abuse
Authenticate guests before creating holds so anonymous bots cannot block calendars. Rate-limit hold creation per user, device, IP, and listing, especially on high-demand dates. Use risk scoring for suspicious payment methods, impossible travel patterns, repeated holds that never confirm, and accounts that try to lock many properties at once. Risk checks should have a time budget; if review needs hours, do not keep public inventory blocked unless product explicitly chooses that cost.
Protect payment and tax data. Keep card data inside the payment provider boundary, store provider tokens rather than raw card details, and avoid logging full billing addresses or tax identifiers. Host calendar URLs can leak availability, so treat imported calendar links as secrets. Admin tools that change calendars or refunds need audit logs because a bad admin action can create the same damage as a race condition.
Cost awareness
The expensive part is not only servers. A double booking creates refunds, relocation support, host compensation, bad reviews, and sometimes legal work. That is why exactness at checkout is worth more than shaving a few milliseconds with unsafe cached writes.
At the same time, making every availability browse hit the primary calendar is wasteful. Use cache and indexes for browsing, then pay the cost of a short transaction only when the guest tries to reserve. Holds also have a cost: long holds raise conversion but hide inventory from other guests. Payment authorization fees, SMS/email, tax calculations, and fraud reviews all add cost, so move them off the critical path where correctness allows.
Production scenes
Scene 1: The holiday home that double-booked
The moment is ugly: two families receive confirmation for the same home on the same weekend. The dashboard may show a small number of conflicts, but each one is a high-trust incident. Support must relocate one guest, the host is angry, and finance may need to unwind one payment.
The trap is usually a split between cached availability and booking truth. The system checked the cache, then inserted a reservation later, or it wrote a reservation row without a per-night uniqueness rule. The fix is not better copy. The fix is one authoritative transaction or database constraint that makes overlap impossible for active reservations.
What to build: a duplicate-night detector that pages immediately, a database rule that rejects active overlap, idempotency on booking, and a dashboard that links reservation id, payment intent, and listing-night rows.
Scene 2: Ghost holds block the calendar
A guest starts checkout and leaves. The dates stay unavailable for hours. Hosts see open demand but cannot sell the nights. Guests see fewer options than reality. The API may look healthy because no request is failing; the problem is stale state.
The trap is treating held_until as a suggestion instead of an operated clock. If the sweeper is broken, or payment reconciliation never releases failed holds, the calendar slowly fills with ghosts. Watch the age of held reservations and alert when holds survive past their allowed window.
Scene 3: Payment is authorized, but booking is unclear
The guest's bank shows a pending charge while the app says the reservation is processing. The user tries again, and now support has to explain two pending authorizations. This happens when the worker crashes or times out between payment and booking state changes.
The fix is a clear state machine and reconciliation job. Every payment intent belongs to one reservation. Retrying capture or void uses the same idempotency key. If the provider says money was captured but the reservation did not confirm, the system moves the reservation to a review state and stops trying random new charges.
Scene 4: Host calendar sync imports a conflict
The host connects another platform's calendar after accepting a booking. The import says the same weekend is blocked elsewhere. A naive system might mark the nights unavailable and confuse the confirmed guest. A safer system keeps the confirmed reservation, creates a conflict event, alerts the host, and blocks future sales until someone resolves the mismatch.
In the interview: Pick one production scene and tell it as a short story. For this system, the strongest story is two guests racing for one night because it proves you understand the core invariant.
Bottlenecks and tradeoffs
Checkout truth vs search speed
Search wants speed and breadth. Checkout wants certainty. If you force search to be perfectly fresh, the product becomes slow and expensive. If you let checkout trust search, you double-book. The landing point is stale-tolerant browsing plus live re-check and lock on reserve.
Hold length vs host revenue
A long hold gives guests time to pay and reduces checkout frustration, but it hides nights from everyone else. A short hold improves host availability but increases failed checkouts. Choose a hold TTL, measure conversion and expired holds, and make the product decision explicit.
Payment certainty vs inventory time
Payment providers are slower and less reliable than a local database transaction. Holding inventory while waiting for payment is necessary, but holding it forever is not. Keep a short pending state, reconcile unclear provider results, and release on timeout.
Strict calendar sync vs host flexibility
External calendars reduce host mistakes, but they arrive late and can conflict with confirmed bookings. Treat your reservation database as the source of truth for online bookings, and treat imports as blocks or conflicts that need reconciliation.
Interview tips
Check availability is not the same as book
You might say: "The app checks whether the dates are free, and then the guest can pay."
Interview Push: "Two guests both checked availability a second ago. Which one owns the dates?"
Land here: The check did not reserve anything. It may have read from cache or a search index. Ownership starts only when the reserve or confirm request locks the authoritative listing-night rows and commits a hold or booking. Say this clearly because it is the center of the design: browsing can be fast and slightly stale; booking must be exact.
Two guests racing need one database winner
You might say: "We insert a reservation row after the user pays."
Interview Push: "What prevents another server from inserting an overlapping reservation at the same time?"
Land here: Payment is not the lock. The calendar transaction is the lock. Use one row per night with row locks, or a database rule that rejects active overlap. The first transaction commits and the second gets 409. Only after the system owns a hold should it move forward with payment authorization.
Payment retries must not create new bookings or charges
You might say: "If payment times out, the client can call confirm again."
Interview Push: "What if the provider captured money but your API timed out before the client saw success?"
Land here: Store a payment intent linked to the reservation, and use idempotency keys for authorization, capture, and refund. A retry asks for the same logical result. Reconciliation checks the provider before making a new money movement. The user should not be charged twice because a phone network dropped a response.
Calendar imports are not stronger than confirmed reservations
You might say: "We import the host's iCal feed and update availability."
Interview Push: "The import overlaps a confirmed reservation. Do you delete the guest booking?"
Land here: No. Confirmed reservations from the booking database remain the source of truth. The import creates a conflict record and alerts the host. Future availability may be blocked until resolved, but the system should not silently undo a guest confirmation because an external feed arrived late.
Pricing belongs to the hold window
You might say: "We compute price at checkout."
Interview Push: "What if weekend pricing or tax rules change while the guest is paying?"
Land here: Create a quote with line items, currency, tax rules, and expiry. The reservation hold stores the quote id. Confirmation validates the quote is still usable, so the amount the guest accepted is the amount captured. If the quote expired, return a clear error and ask the guest to refresh pricing.
What should stick
- Search is a hint. Availability browsing can be cached and slightly stale because checkout re-checks live inventory.
- Booking is the lock. Reserve or confirm must own the listing-night rows in one short transaction.
- Hold then confirm. A short hold protects checkout while payment authorization runs, but expired holds must be released.
- Money is idempotent. Authorization, capture, and refund retries return the same logical result instead of double charging.
- Calendar sync is reconciliation. External feeds can block future dates or raise conflicts, but they do not silently override confirmed reservations.
Tell it in the room: "I keep listing-night rows as the source of truth. GET availability may be stale. POST reserve locks the live nights and creates a hold with an idempotency key. Payment authorizes, confirm captures and marks booked, then outbox workers send email and sync calendars. The second guest racing for the same night gets 409."
Key Takeaways
- Start with the invariant: no overlapping confirmed reservation for the same listing-night.
- Model listing-local nights and half-open date ranges so check-in/check-out boundaries are clear.
- Use a short hold with expiry before payment confirmation.
- Lock quote, taxes, and fees during the hold window so price does not surprise the guest.
- Keep payment, refund, email, search, and calendar sync idempotent and traceable.
- Treat external calendars as delayed inputs that can create conflicts, not as stronger truth than the booking database.
Related Topics
- Booking Waitlist System Design - Offer cancelled inventory without racing users.
- Vaccine Booking System Design - Holds, fairness, and last-slot contention.
- Payment Gateway System Design - Idempotent authorization, capture, and refund patterns.
- Notification System Design - Async confirmation messages after commit.
- Rate Limiter System Design - Abuse protection for hot checkout paths.
Quick revision notes
Clarify the product boundary. Say you are designing reservation truth, not the whole rental marketplace. Ask about instant book, hold duration, payment timing, stale search, and external calendar sync.
Draw two availability paths. Browsing can be fast and slightly stale. Reserving runs on the authoritative calendar and either creates a hold or returns 409.
Remember the race. Two guests can both see available. Only the transaction that locks the listing-night rows owns the nights.
Keep money and inventory tied by ids. Reservation id, quote id, payment intent id, and idempotency key make retries and support investigations possible.
Operate the clocks. Watch hold age, payment pending age, outbox age, calendar sync conflicts, and stale-search conflicts.
Practice next: Open Airbnb Reservation System Design on Practice System Design and run a timed attempt using the check-availability-vs-booking race as your main walkthrough.
What interviewers expect
A strong answer separates check availability from book, uses an authoritative calendar store, explains hold to confirmed transitions, handles payment idempotency, treats search as a hint, adds a cancellation and refund path, and calls out fraud and host calendar sync as important but bounded concerns.
Interview workflow (template)
- Clarify requirements. Confirm functional scope, users, consistency needs, and which non-functional goals matter most (latency, availability, cost).
- Rough capacity. Estimate QPS, storage, and bandwidth so your data model and partitioning story are grounded.
- APIs and core flows. Define a minimal API and walk 1–2 critical read/write paths end to end.
- Data model and storage. Choose stores for each access pattern; call out hot keys, indexes, and retention.
- Scale and failure. Add caching, sharding, replication, queues, or fan-out as needed; say what breaks in failure modes.
- Tradeoffs. Name alternatives you rejected and why (e.g. strong vs eventual consistency, sync vs async).
Frequently asked follow-ups
- How do you prevent double booking for overlapping date ranges?
- What is the difference between check availability and book?
- How do holds, payment authorization, and confirmation work together?
- How do cancellations return nights and trigger refunds?
- How do external host calendars sync without corrupting the source of truth?
Deep-dive questions and strong answer outlines
How do you stop two guests from booking the same night?
I would make checkout re-check availability inside one short transaction on the authoritative calendar. The system either locks one row per night or writes reservation-night rows with a uniqueness rule on listing and date. The first reservation commits; the second receives a clear 409 conflict and must choose new dates.
Why is GET availability not enough for checkout?
Availability reads can come from a cache or search index and may be slightly stale. They help the guest browse, but they do not reserve anything. POST reserve or confirm must read the current calendar on the primary store and take the hold or booking there.
How do payments fit the reservation state machine?
Create a short hold first, authorize the card with an idempotency key, then capture only when the reservation moves to confirmed. If capture fails, release the hold. If a worker retries after a timeout, the payment provider and booking system both return the same logical result instead of charging twice.
How do cancellations and refunds work?
Cancellation is a state transition with policy. The booking service checks the cancellation rule, computes refund lines, releases the nights in the same transaction, writes an outbox event, and calls the payment provider with an idempotent refund key.
How do host calendar imports work?
Imported iCal or channel-manager blocks are advisory until reconciled against the booking calendar. Confirmed reservations from this system win for online inventory. Conflicts create host-visible warnings and block future availability, but they do not silently delete confirmed guest bookings.
AI feedback on your design
After a practice session, InterviewCrafted summarizes strengths, gaps, and interviewer-style expectations—similar to a written debrief. See a static example report, then practice this problem to get feedback on your own answer.
FAQs
Q: Are we designing all of Airbnb?
A: No. Keep search ranking, reviews, messaging, host onboarding, and dispute tooling out of scope unless the interviewer expands the prompt. This guide focuses on reservation truth: dates, holds, payment, cancellation, and calendar sync.
Q: Should availability search be strongly consistent?
A: Usually no. Browsing can tolerate a small delay because checkout re-checks the authoritative calendar. The user may occasionally see a date that disappears at book time, but that is safer than slowing every search request with heavy locks.
Q: What about multi-unit listings such as a hotel room type?
A: Model inventory count per night instead of a single booked/free bit. The same rule still applies: the transaction must not let the count fall below zero, and the guest sees a conflict if another reservation wins the last unit.
Q: Where do taxes and fees belong?
A: A pricing service returns a quote with nightly rate, cleaning fee, service fee, taxes, and currency. The hold stores that quote version so checkout cannot silently change the amount while the guest is paying.
Q: What does a successful reservation response promise?
A: It promises that the reservation state was saved and the calendar nights are owned by that reservation. Email, host calendar export, analytics, and search-index updates happen after commit and may retry without changing that promise.
Practice interactively
Open the practice session to use the canvas and stages, then review AI feedback.