Course Content
Mobile System Design Interview
11 sections · 23 lessons
Hotel Reservation: requirements, the offline split and the API contract
Prompt: "Design a hotel reservation app."
The distinctive thing about this problem is not search and it is not the booking form. It is that the user moves through five or six screens over several minutes, the operating system can destroy the app between any two of them, and at the end of the sequence real money moves.
Every other case study in this course can lose a little state and recover. This one cannot. This lesson sets up the problem and the API that makes it safe; the next designs the persisted flow, the payment step and the follow-ups.
The questions worth asking
"Search, book, or manage existing bookings — or all three?" Take all three, and note that they have completely different offline requirements. Search needs live availability and cannot work offline. Viewing a booking you already hold absolutely must.
"Is payment in-app, or handed to a provider?" This changes the design substantially. A hosted provider flow — a web view or a platform payment sheet — means card details never touch your code, which removes a large compliance burden. Ask, then assume a provider software development kit, because that is what most teams do and it is the more interesting client problem.
"What are the offline requirements?" Answer it yourself with the split above: searching offline is meaningless because availability changes minute to minute, but a traveller standing at a hotel reception desk with no signal needs their confirmation number. That contrast is worth stating explicitly — it demonstrates that "works offline" is a per-feature decision, not an app-level one.
"Guest checkout, or account required?" Guest checkout means the booking is not attached to an account, so the confirmation on the device may be the user's only copy. That raises the stakes on local persistence considerably.
"Multiple rooms, multiple travellers, loyalty programmes?" Ask, and scope them out unless the interviewer wants them. They add form complexity without adding design insight.
The scope to state back
In scope: search with filters, a hotel and room detail screen, a booking flow of several screens ending in payment, a confirmation, and a bookings list that works offline.
Out of scope: the availability and pricing engine, the payment processor's internals, and inventory management. In System Design Interview, Design a Hotel Reservation System designs the reservation server and Design a Payment System designs the payment system.
Requirements and constraints
Functional requirements
- Search hotels by location and dates, with filters and pagination.
- View a hotel, its rooms, and current prices.
- Complete a booking across several screens: room, guest details, payment, review, confirm.
- Pay, and receive a confirmation with a reference number.
- View existing and past bookings, including with no network.
- Cancel a booking within its policy.
The non-functional targets
| Target | Value | Why |
|---|---|---|
| Search results to screen | Under ~1.5 s on 4G | The user is comparing options and will retry elsewhere |
| Flow state persistence | Every step transition, under ~50 ms | It must be on disk before the screen changes |
| Price quote validity | Typically 10–15 minutes, set by the server | Long enough to finish, short enough to be honest |
| Booking outcome certainty | Always resolvable | The user must never be left wondering whether they paid |
| Existing booking availability | 100% offline | This is when it is needed |
The four constraints
Network. Search fails gracefully; booking must not. The dangerous moment is the payment request, where a lost response is ambiguous rather than merely inconvenient.
Battery and storage. Modest here. Search results are text and a few images; bookings are a handful of rows. This is one of the few designs where these are not the interesting constraints, and saying so is better than manufacturing concern.
Memory. Also modest, with one exception: hotel photo galleries. Apply Images and media — downsample to display size, one full-resolution image at a time in a pager.
Process death. The constraint that defines the design. A user tapping between the booking flow and their email app to find a passport number is a completely ordinary sequence, and the app may not survive it.
The constraint in one sentence
The offline story, per feature
Being explicit per feature is the strong answer here, and it is worth writing on the board:
| Feature | Offline behaviour |
|---|---|
| Search | Unavailable. Show the last results with their capture time and a clear "prices and availability may have changed — reconnect to search" state, and disable booking from them |
| Hotel detail | Cached text and images render; the price panel is replaced by a reconnect prompt |
| Booking flow | Cannot start. If already in progress, state is preserved and the flow waits at the current step with a retry |
| Payment | Blocked. Never queued — see Payment safety on the client |
| My bookings | Fully available: reference number, dates, address, cancellation policy, and a map snapshot stored as an image |
That last row is the one that matters to a real traveller, and it is the one candidates forget.
The API contract
With the constraint named, the API follows from it: four endpoints, and one structural decision that carries the whole design.
Search
GET /v1/hotels/search?lat=&lng=&radius_km=&check_in=&check_out= &guests=&filters=&limit=20&cursor=<opaque>→ { items: [ { hotel_id, name, rating, thumbnail: {url,w,h}, from_price: { amount, currency }, distance_m } ], next_cursor, search_id, server_time }Cursor pagination as always. search_id is worth asking for: it lets the server keep the result set stable while the user pages, so a price change does not reshuffle results underneath them. Prices here are indicative — the field is named from_price deliberately, because a search result is not an offer.
Quote, and why it is a separate call
POST /v1/quotes { hotel_id, room_type_id, check_in, check_out, guests }→ { quote_id, total: { amount, currency }, breakdown: [ { label, amount } ], expires_at: "2026-08-30T09:29:00Z", cancellation_policy: { … } }The naive design skips this: the user picks a room and the app books it at the price shown in search. It fails in a way that is worse than a bug, because it is a legal and trust problem — the search price was indicative, computed minutes earlier, possibly before taxes and fees, and possibly for a room that has since sold. Charging a different amount than the one displayed is not acceptable, and neither is holding a price forever.
So the quote step exists to convert an indicative price into a binding, expiring offer. The client shows exactly the quoted total, displays the expiry, and must handle it lapsing — a visible countdown in the last couple of minutes, and a re-quote if it expires, with the new price shown as a change rather than substituted silently.
Booking, and the key that makes it safe
POST /v1/bookings Idempotency-Key: 6f2a1e88-… ← generated on the device, once { quote_id, guest: {…}, payment_token: "tok_…" }→ 201 { booking_id, reference: "HX7K2M", status: "confirmed", … }→ 409 { error: "quote_expired", new_quote: {…} }→ 200 { … } // same key seen before: the original booking, not a new oneThe idempotency key is generated once when the user taps Confirm, stored with the flow state, and reused on every retry of that attempt. It is what makes the ambiguous failure in Payment safety on the client recoverable, and it must be asked for explicitly — a server that ignores the header gives you nothing.
Retrieving a booking
GET /v1/bookings?idempotency_key=6f2a1e88-… → the booking, or 404GET /v1/bookings → the user's bookingsThe first form is the reconciliation endpoint. It is the single most valuable thing to request from the backend team in this design, and Design a Payment System in System Design Interview covers how the server side of it works.
Offline: search and quote fail with a network error and a retry. POST /v1/bookings is never attempted offline — the flow blocks at the review step with a clear message. GET /v1/bookings responses are stored, so the bookings list is served from the database always.