The system design question
Design a hotel search and availability system. Given a city and a date range, return the hotels with rooms available for every night, and make sure it holds up under high traffic.
Agree on the scope (5 minutes)
Say up front what you're covering.
| We'll design | We'll skip |
|---|---|
| Searching a city for a date range | The booking and payment flow |
| Checking every night of the stay | Ranking and personalization |
| Keeping availability up to date as rooms sell | Maps and filters like price or stars |
| Staying fast during traffic spikes | Reviews and photos |
Then agree on what "good" means:
- Fast: search results come back in a few hundred milliseconds, even for big cities.
- Correct enough: results can be a few seconds stale, but we check again for real when someone books.
- Holds up under load: a summer holiday rush shouldn't slow search down or touch the booking database.
- Every night counts: a hotel only shows up if it has a room free for every night of the stay.
Rough numbers (5 minutes)
These are assumptions, made up but reasonable: 1 million properties, about 20 room types each, availability kept for a year ahead, 100 million searches a day with peaks at 10 times the average, and 50 million availability changes a day from bookings and hotels updating their rooms.
- ~1.2K
- searches per second, on average
- ~12K
- searches per second at a peak
- ~600
- availability changes per second
- ~7B
- room type and night combinations
| Question | Rough math | What it means |
|---|---|---|
| How much availability data? | 1 million properties × 20 room types × 365 nights | About 7 billion rows. At about 20 bytes each that's around 150 GB, which fits in memory across a handful of machines |
| How much work is one search? | A big city with 3,000 hotels × 20 room types × 3 nights | About 180K checks per search, way too many to run against a database 12K times a second |
| Reads versus writes? | 12K searches a second against 600 changes a second | Reads win by a lot, so it's worth doing extra work on each write to make reads cheap |
The overall design (10 minutes)
- The inventory database holds how many rooms of each type are free on each night. Bookings and hotel admins write to it, and nothing else does.
- Every change to it gets published to a change stream, like 'Hotel 42, double room, June 3: 4 left'.
- Availability index servers read that stream and keep a compact copy of availability in memory, split up by city.
- A search goes to the servers holding that city. They check every hotel against every night of the stay and return the ones that pass.
- The search service adds hotel details from a cache, ranks the list, and sends back the first page.
What the apps need to do
| Action | Who does it | What's sent |
|---|---|---|
| Search | Guest | City, check-in, check-out, number of guests, page |
| Get a hotel's rooms for dates | Guest | Hotel ID, check-in, check-out |
| Update rooms or prices | Hotel admin | Hotel ID, room type, nights, rooms free, price |
| Availability changed | Internal stream | Hotel ID, room type, night, rooms free, price |
What we store and where
| Data | Where it goes | Why |
|---|---|---|
| Rooms free per room type per night | Relational database, split by hotel | The source of truth that bookings check against |
| Availability index | In memory on index servers, split by city | So a search can check thousands of hotels without a database call |
| Hotel details like name and photos | A cache in front of a hotel database | Changes rarely and gets read on every search |
| Popular search results | Short-lived cache | Takes the edge off spikes for the same city and dates |
Step 1: Checking every night fast (12 minutes)
The tricky part is 'every night'. A hotel with rooms free on June 3 and 5 but not June 4 has to drop out for a June 3 to 6 stay. If you think of each hotel as a row and each night as a column, you need the hotels whose cells are all 'yes' for the columns in the range.
So for each hotel, the index keeps one small number per night: the most guests any free room type could fit, or zero if it's sold out. A search loops over the hotels in the city and checks just those few nights. For 3,000 hotels and a 3-night stay that's about 9,000 lookups in memory, which takes well under a millisecond.
Keep it as a plain array per hotel, indexed by days from today. Checking a range is then just reading a few numbers next to each other. You can get fancier with bitmaps, where each night is one bit and you combine them in one step, but a simple array is fast enough to start.
Why not just ask the database?
You could, with a query that groups by hotel and checks the count of free nights matches the stay length. It works for a small site. At 12K searches a second, each touching thousands of rows, the database would melt, and it's also the thing bookings depend on. Keeping search off it protects bookings. That's a classic case for picking the storage that matches how you read, which is the point of relational vs non-relational thinking.
Step 2: Keeping availability fresh (10 minutes)
When a room sells, the booking writes to the inventory database, and the change goes onto a stream. Index servers read the stream and update their arrays. Most changes show up in search within a second or two.
That means search can be slightly wrong. Someone might see a hotel that sold its last room two seconds ago. That's fine, because the booking step checks the database for real and won't let it go through. We're picking speed for search and strict correctness for booking, a trade-off you'll see described under consistency.
If an index server restarts, it loads a recent snapshot of its cities and then replays the stream from where the snapshot left off. Each change carries the full new value, like '4 left', so replaying one twice does no harm.
Step 3: Handling traffic spikes (8 minutes)
Split the index by city, a lot like sharding, and keep several copies of each split. Busy cities like Paris or London get more copies, so a rush there just means sending searches to more servers.
Lots of people search the same city and dates, especially around holidays. A short-lived cache of results, say 30 seconds, keyed by city, dates and guests, absorbs a lot of that. A short expiry keeps it from going too stale.
If things still get overloaded, cut back on the extras first, like fancy ranking or showing prices for every room, before you start rejecting searches.
Choices you should be able to defend
| We chose | Instead of | Because | We'd reconsider if |
|---|---|---|---|
| A separate in-memory search index | Querying the bookings database | Search traffic can't slow down bookings | Traffic was small enough for one database |
| Search a second or two stale | Always exact search results | Exact results would need every search to hit the source of truth | Never for search, since booking checks again anyway |
| Splitting the index by city | Splitting by hotel | A search only ever touches one city, so it goes to one group of servers | Searches often spanned many cities, like a whole region |
| Short cache on popular searches | No cache, or a long one | Takes the edge off spikes without showing old data for long | Hit rates were too low to bother |
What could go wrong (5 minutes)
| What happens | What we do about it |
|---|---|
| An index server dies | Other copies of that city keep serving. A new one loads a snapshot and catches up from the stream |
| The change stream falls behind | Search gets more stale but keeps working. Booking still checks the database, so nobody gets a room that isn't there |
| A guest books a room search said was free, but it's gone | The booking fails cleanly, and we show other rooms at that hotel or nearby |
| A huge holiday spike | Add index copies for busy cities, lean on the results cache, and drop extras before rejecting searches |
| A hotel bulk updates a year of rooms | That's a lot of stream events, so we batch them per hotel and apply them in one go |
Follow-up questions (5 minutes)
How would you add a price filter?
Store the cheapest price per night next to the availability number, and add them up over the stay while you check. Then filtering by total price is one more comparison.
What about stays longer than the index covers?
Cap search at something like 30 nights and a year ahead. Longer stays go to a slower path that asks the database directly.
How would search show a hotel that has rooms if you switch room types mid-stay?
Most sites don't offer that by default. If we did, the check becomes 'some room free each night' instead of 'one room type free every night', which our per-hotel number already handles.
How the 60 minutes add up
| Minutes | What you're doing |
|---|---|
| 0–5 | Agree on what you're building |
| 5–10 | Rough numbers |
| 10–20 | Draw the overall design |
| 20–32 | Checking every night fast |
| 32–42 | Keeping availability fresh |
| 42–50 | Handling traffic spikes |
| 50–55 | What could go wrong |
| 55–60 | Follow-up questions |
Key concepts used here
- Caching strategiesMake reads faster, and figure out what to do when data changes.
- Relational vs non-relational databasesPick a database based on how you'll actually use it.
- Consistency and the CAP theoremWhat happens when copies of your data can't agree.
- Sharding and consistent hashingSplit data across machines and keep it balanced.
- Message queues and event streamsKeep services from depending on each other, and handle traffic spikes.
- Search and inverted indexesFind text fast, rank what comes back, and keep the index fresh.
Further reading
We wrote this sample answer ourselves.
Practice Loop is independent and isn't affiliated with or endorsed by Booking.com.