Hotel booking does not usually break on the search page. It breaks on the availability calendar, where one room category is sold across date ranges and a small mistake quickly turns into oversell.
This chapter connects inventory by date, controlled overbooking, reservation confirmation, cancellations, and the path from a single service to sharded storage.
For interviews and engineering discussions, this case is useful because it forces you to talk about calendar correctness and contention around limited inventory instead of hiding behind generic marketplace language.
Availability Calendar
Inventory does not live in one number per product here. It lives in a date grid, so the model must handle stay ranges, cancellations, and limit recalculation cleanly.
Overbooking Window
Controlled oversell may be good for the business, but its limits need to be explicit in the model rather than hidden inside one service.
Room Contention
The most painful race happens on the last room of a category, so the confirmation path has to be designed for conflicts from the start.
Date Partitioning
Historical dates quickly inflate the tables, and dealing with older partitions becomes a separate operational concern.
This public ArchDays 2022 interview is a good example of why hotel booking is mostly a calendar-availability problem rather than a catalog problem. Once the business allows controlled overbooking, the architecture has to protect the room counters, the reservation-confirmation path, and the cancellation flow explicitly.
Related case
Designing Airbnb
A similar problem with availability calendars, dynamic pricing, and competition for limited inventory.
Interview video
The full public interview is available on YouTube. It is useful to watch before reading the chapter so you can see how the requirements are clarified and how the design evolves step by step.
Watch on YouTubeProblem statement
Hotel booking system
Design a service for the Russian market that shows available hotel options, creates reservations for a chosen date range, and takes the user through payment and cancellation.
Rough order of magnitude for hotels in the system
Total number of rooms across all properties
Functional requirements
Hotel details
Detailed hotel information, room categories, and stay rules.
Create reservation
Pick dates, choose a room type, and confirm the booking online.
Payment
Integrate with the payment system and record the payment result.
Cancel reservation
Cancel an existing booking and return the room to available inventory.
Key business requirement: controlled oversell
The business intentionally allows a limited amount of overbooking because some guests cancel or never arrive. The architecture has to model this rule explicitly instead of treating it as an accidental side effect.
What is out of scope
Load estimation
Start with a quick back-of-the-envelope estimate. It helps separate the places where scale really matters from the places where we should keep the design simple.
Important: the average is small, but seasonal peaks, holidays, and promotions can easily create 10x–100x spikes. The system should be designed for those short windows of heavy contention as well.
Core API
/v1/hotels/{hotel_id}Fetch the hotel page and general offer information
/v1/hotels/{hotel_id}/rooms/{room_type_id}Fetch details for a specific room category
/v1/reservationsCreate a reservation for a chosen date range
{
"hotel_id": "123",
"room_type_id": "456",
"start_date": "2024-03-15",
"end_date": "2024-03-20",
"guest_info": { ... }
}/v1/reservations/{reservation_id}Cancel a reservation
What the API should define early
In a real system, reservation creation and payment callbacks need clear idempotency rules so a retry after a timeout does not create a second booking or charge the guest twice.
Data-model evolution
The real question is not how to store a reservation row. It is how to model availability per room type and per date so that reads stay cheap and updates stay safe.
Naive approach: one reservations table
Reservation ├── id ├── hotel_id ├── room_type_id ├── room_id (specific room) ├── start_date ├── end_date ├── status └── guest_id
Problem 1: availability checks require COUNT across reservations for every requested date.
Problem 2: controlled oversell becomes a separate layer of custom business logic.
Problem 3: concurrent requests can race for the last room of a category.
Better approach: inventory by date
RoomTypeInventory ├── hotel_id ├── room_type_id ├── date ├── total_rooms (total rooms of this type) ├── total_reserved (reserved) └── overbooking_limit (overbooking limit) Constraint: total_reserved <= total_rooms * (1 + overbooking_limit)
Advantage 1: availability reads become a point lookup instead of a scan across all reservations.
Advantage 2: controlled oversell is now an explicit part of the model.
Advantage 3: the counter can be updated atomically with a conditional UPDATE.
Handling concurrency
The critical reservation path needs strict correctness: if two users try to book the last room at the same time, the system must still confirm only the allowed number of reservations.
In practice, the design usually comes down to optimistic versus pessimistic locking. The real comparison is not just correctness, but also throughput under peak demand and the risk of deadlocks.
Pessimistic locking
BEGIN; SELECT * FROM room_inventory WHERE hotel_id = ? AND room_type_id = ? AND date = ? FOR UPDATE; -- Check the limit and update the counter UPDATE room_inventory SET total_reserved = total_reserved + 1 ... COMMIT;
Pros
Straightforward logic and very explicit correctness guarantees
Cons
Row locking, lower throughput, and deadlock risk
Optimistic locking
-- Isolation Level: REPEATABLE READ BEGIN; UPDATE room_inventory SET total_reserved = total_reserved + 1 WHERE hotel_id = ? AND room_type_id = ? AND date = ? AND total_reserved < total_rooms * (1 + overbooking_limit); -- If affected_rows = 0, the limit is already exhausted COMMIT;
Pros
Higher throughput, no explicit row locks, atomic conditional update
Cons
Requires retries and careful conflict handling
Database-level constraint
ALTER TABLE room_inventory ADD CONSTRAINT check_overbooking CHECK (total_reserved <= total_rooms * (1 + overbooking_limit));
This is the final guardrail: even if the service code is wrong, the database still prevents the system from crossing the allowed limit.
A reasonable choice for this scale
For an average load around 2.3 TPS and peaks up to 100 TPS, optimistic locking is usually the more practical choice. It keeps the design simple while leaving enough headroom for spikes. The pessimistic option becomes more interesting only when contention on a specific inventory slice turns extreme.
Scaling strategies
The system usually grows along two axes: time and the number of hotels. At that point, you need a clear story for partitioning by date, sharding by hotel, using consistent hashing when needed, and keeping maintenance work manageable.
Time partitioning
Inventory tables map naturally to months or quarters because booking is date-driven by design.
- Historical periods are easier to archive and prune
- Queries touch a narrower slice of data
- Operational work on old partitions becomes predictable
Partitioning by hotel_id
Once one cluster is no longer enough, data can be distributed across shards by hotel identifier.
- Write load and inventory counters scale out more cleanly
- Heavy hotel chains stop interfering with the rest of the system
- Shard assignment can be based on consistent hashing
Key takeaways
Start with the invariants
If you do not clarify the date-range model and the oversell policy early, you can build a neat but fundamentally wrong architecture.
Show the model evolving
A strong answer rarely starts with the perfect schema. It is better to show why the naive reservations table fails and how you move toward inventory by date.
Explain the concurrency choice
Optimistic and pessimistic locking differ by trade-off, not by abstract correctness alone. The interviewer wants to hear why one fits this workload better than the other.
Let scaling follow the shape of the data
Partitioning by time, inventory counters, and hotel-based sharding should all follow from the calendar nature of the problem rather than appear as a generic list of “things to mention”.
Related chapters
- Airbnb - provides a neighboring case with availability calendars, dynamic pricing, and contention around limited inventory.
- Payment System - extends the discussion with transactional correctness, idempotency, and safe handling of payment side effects.
- Hacking the System Design Interview (short summary) - helps structure the interview answer and explain booking-system trade-offs more clearly.
- System design case studies examples - places hotel booking in the wider case-study set and makes cross-domain comparison easier.
