Android System Design Interview Questions
How do you implement a hotel list and detail screen? Discuss the APIs you would create and how the layout will work.
Tier: CommonDifficulty: HardAsked at: booking-com
The trap in this question is designing one API that returns everything and one screen that renders it all at once. Hotel data splits into what's stable, photos, amenities and description, and what's volatile, price and availability. Treating those the same is what makes this kind of screen slow and hard to cache.
What I'd clarify first
- Does search need to support filters, price range, star rating, amenities, and sorting, or is it just a city and a date range.
- Should the list support a map view alongside it, since that changes what the search response needs to carry, coordinates for every result, not just the ones currently visible.
- Does the detail screen need to work offline for a hotel the user already viewed, and if so, is showing a stale price acceptable or does that need to be blocked.
- Is the displayed price the price the user pays, or does the backend add taxes, fees and a currency conversion later, because that gap between the number on the card and the number at checkout is where the complaints come from.
The APIs
Splitting stable data from volatile data across separate endpoints is the core design decision here.
GET /hotels/search, query params for city, dates, guests, filters, and a cursor. Returns a lightweight list, id, name, one thumbnail, star rating, review score, distance, and a starting price. Deliberately thin, this is the payload that has to scroll smoothly. Paging is cursor based rather than by offset, because availability changes while the user scrolls, and an offset into a list that is being rewritten underneath you repeats some hotels and skips others.GET /hotels/{id}, the stable detail, full photo gallery URLs, description, amenities, cancellation policy, exact coordinates. This barely changes hotel to hotel over time, so it's the part worth caching aggressively.GET /hotels/{id}/rooms, separate from the detail call, takes the same dates and guest count as the search, returns room types with live pricing and availability. This is volatile by nature and should never be bundled into the cacheable detail payload, otherwise every price refresh forces a re-fetch of photos and descriptions that haven't changed.GET /hotels/{id}/reviews, paginated on its own, since a popular hotel can have thousands of reviews and the detail screen shouldn't have to load them all to render its header.
How the layout works
The list screen is a LazyColumn driven by Paging 3 through collectAsLazyPagingItems, or a RecyclerView with the paging adapter on a View based codebase. Either way it is one PagingSource calling the search endpoint page by page, so scrolling never has to hold more in memory than what's near the viewport. Each row decodes one downsampled thumbnail, not a full gallery, following the same downsampling and cancellation approach as any image loading pipeline, so a fast scroll through hundreds of hotels doesn't queue hundreds of full-resolution decodes.
Tapping a row navigates to the detail screen, ideally with a shared element transition on the thumbnail so the tap feels continuous rather than a hard cut. On arrival, the detail screen fires the /hotels/{id} call and the /hotels/{id}/rooms call in parallel, not sequentially. Each section renders as its own data comes back, photo gallery and description first if they resolve first, room pricing filled in a beat later rather than blocking the whole screen on the slower call. Reviews load lazily further down, either a "view all" link into their own paginated screen or a small embedded page that fetches more only as the user scrolls into it.
The search criteria are state, not arguments. Dates, guests, filters and the selected room live in a SavedStateHandle rather than in the composable or the fragment, so a rotation or a process death while the user was in the camera app brings them back to the same list with the same dates rather than to an empty search form.
The price the user finally acts on comes back as a server issued quote with an expiry rather than as a bare number, and that quote id is what the checkout flow charges against. That is the bridge between this screen and booking, and it is what stops a user paying yesterday's price for a room that repriced while the app sat in the background.
Tradeoffs I'd call out
- One heavyweight detail endpoint vs several focused ones. A single endpoint is simpler to call, one request, one response, but it means every price refresh drags the whole payload along with it and makes the stable content impossible to cache on its own. Splitting detail from rooms costs an extra round trip but lets the client cache what rarely changes and always hit the network for what does.
- Thin list payload vs richer list items. Keeping the list response to one thumbnail and a starting price keeps scrolling fast, but if product wants something like "3 room types left," that has to be a small aggregate the backend computes and includes, not something the client derives by calling
/roomsfor every visible row, that would turn a scroll into a fan-out of network calls. - Caching a price the user might act on. Caching the stable detail payload for offline viewing is straightforward. Caching a price is riskier, since a stale number is not just an inconvenience but something a user could try to book against. The honest answer is to show cached content with a visible "may be out of date" indicator and require a fresh network check before allowing checkout to proceed.
What breaks at scale, offline, and on a poor connection
At scale, city-level searches can return thousands of results, so pagination has to be server-side, and filter changes need to be debounced client-side rather than firing a new search on every keystroke of a price slider. On a poor connection, the parallel detail and rooms calls should fail independently, if pricing times out, the screen should still show photos and amenities with a retry affordance just for that section, not throw away everything that did load. Offline, a previously viewed hotel's stable detail can render from a Room-backed cache, but booking actions should be disabled until connectivity and a fresh price check succeed, since committing to a number that might already be wrong is a business risk, not just a UX one.
Watch