Target latency:
GET /v1/hotels/search
{
"location": "New York",
"check_in": "2026-07-01",
"check_out": "2026-07-05",
"guests": 2,
"min_price": 100,
"max_price": 500,
"amenities": ["wifi", "pool"],
"sort": "price_asc",
"page_token": "abc123"
}
{
"hotels": [
{
"hotel_id": "h_123",
"name": "Central Park Hotel",
"location": "New York",
"rating": 4.6,
"starting_price": 240,
"available": true,
"amenities": ["wifi", "gym", "pool"]
}
],
"next_page_token": "def456"
}
GET /v1/hotels/{hotel_id}
{
"hotel_id": "h_123",
"name": "Central Park Hotel",
"description": "Luxury hotel near Central Park",
"rooms": [
{
"room_type_id": "rt_101",
"name": "Deluxe King",
"base_price": 240,
"capacity": 2,
"amenities": ["wifi", "city_view"]
}
],
"reviews_summary": {
"rating": 4.6,
"review_count": 1832
}
}
GET /v1/hotels/{hotel_id}/availability
{
"check_in": "2026-07-01",
"check_out": "2026-07-05",
"guests": 2
}
{
"hotel_id": "h_123",
"available_rooms": [
{
"room_type_id": "rt_101",
"available_count": 3,
"nightly_price": 240,
"total_price": 960
}
]
}
POST /v1/bookings
Idempotency-Key: booking_req_abc123
{
"user_id": "u_123",
"hotel_id": "h_123",
"room_type_id": "rt_101",
"check_in": "2026-07-01",
"check_out": "2026-07-05",
"guests": 2,
"payment_method_id": "pm_456"
}
{
"booking_id": "b_789",
"status": "CONFIRMED",
"total_price": 960,
"confirmation_code": "NYC-ABC-789"
}
POST /v1/bookings/{booking_id}/cancel
Idempotency-Key: cancel_req_123
{
"booking_id": "b_789",
"status": "CANCELLED",
"refund_status": "PROCESSING"
}
PATCH /v1/bookings/{booking_id}
Idempotency-Key: modify_req_123
{
"new_check_in": "2026-07-02",
"new_check_out": "2026-07-06",
"room_type_id": "rt_101"
}
POST /v1/hotels/{hotel_id}/reviews
{
"booking_id": "b_789",
"user_id": "u_123",
"rating": 5,
"comment": "Great stay and excellent location."
}
POST /v1/hosts/{host_id}/hotels
PATCH /v1/hosts/{host_id}/hotels/{hotel_id}
POST /v1/hosts/{host_id}/hotels/{hotel_id}/room-types
PATCH /v1/hosts/{host_id}/room-types/{room_type_id}/inventory
Responsibilities:
Responsibilities:
Responsibilities:
Responsibilities:
Responsibilities:
Responsibilities:
Responsibilities:
Responsibilities:
Responsibilities:
Responsibilities:
| Data Storage Reason | ||
| Users | Relational DB | Strong identity consistency |
| Hotels | Relational DB | Structured property data |
| Room inventory | Relational DB / distributed SQL | Strong consistency |
| Bookings | Relational DB / distributed SQL | Transactional correctness |
| Payments | Relational DB | Auditability and idempotency |
| Reviews | NoSQL or relational DB | High write/read scale |
| Search index | Elasticsearch / OpenSearch | Fast filtering and geo search |
| Cache | Redis / Memcached | Low-latency hot reads |
| Events | Kafka / Pulsar | Async workflows |
INITIATED
-> INVENTORY_HELD
-> PAYMENT_AUTHORIZED
-> CONFIRMED
Failure states:
-> PAYMENT_FAILED
-> HOLD_EXPIRED
-> CANCELLED
-> REFUNDED
PENDING state.Search Service uses OpenSearch / Elasticsearch.
Indexed fields:
{
"hotel_id": "h_123",
"name": "Central Park Hotel",
"location": {
"lat": 40.785,
"lon": -73.968
},
"city": "New York",
"amenities": ["wifi", "pool", "gym"],
"rating": 4.6,
"price_bucket": "200-300",
"host_id": "host_123"
}
The search index should not be the source of truth for availability.
Recommended approach:
This balances performance and correctness.
location + date_range + guests + filtersSearch results may briefly show stale availability, but final booking is protected by the strongly consistent Availability Service.
This is the most correctness-sensitive part of the system.
Instead of storing every physical room as a row for every date, store inventory by:
hotel_id
room_type_id
date
total_inventory
reserved_count
held_count
Example:
CREATE TABLE room_inventory (
hotel_id BIGINT,
room_type_id BIGINT,
stay_date DATE,
total_inventory INT,
reserved_count INT,
held_count INT,
version BIGINT,
PRIMARY KEY (hotel_id, room_type_id, stay_date)
);
A booking from July 1 to July 5 consumes inventory for:
2026-07-01
2026-07-02
2026-07-03
2026-07-04
Checkout date is not consumed.
For each requested date, Availability Service performs an atomic conditional update.
UPDATE room_inventory
SET held_count = held_count + 1,
version = version + 1
WHERE hotel_id = :hotel_id
AND room_type_id = :room_type_id
AND stay_date = :stay_date
AND total_inventory - reserved_count - held_count > 0;
The hold succeeds only if every date in the requested range is updated successfully.
If any date fails:
For high-scale systems, this can be implemented using:
When a user starts booking, the system creates a short-lived inventory hold.
hold_id
user_id
hotel_id
room_type_id
date_range
expires_at
status
Typical TTL:
5 to 10 minutes
If payment is not completed before expiration:
HOLD_EXPIRED.This prevents a user from locking inventory indefinitely.
CREATE TABLE bookings (
booking_id BIGINT PRIMARY KEY,
user_id BIGINT,
hotel_id BIGINT,
room_type_id BIGINT,
check_in DATE,
check_out DATE,
guests INT,
status VARCHAR(32),
total_price DECIMAL,
idempotency_key VARCHAR(128),
created_at TIMESTAMP,
updated_at TIMESTAMP,
UNIQUE(user_id, idempotency_key)
);
1.Receive booking request
2.Check idempotency key
3.Create booking with PENDING status
4.Request inventory hold
5.Authorize payment
6.Convert hold to confirmed reservation
7.Mark booking CONFIRMED
8.Publish BookingConfirmed event
9.Send notification asynchronously
| Failure Handling | |
| Inventory hold fails | Mark booking as FAILED / SOLD_OUT |
| Payment authorization fails | Release hold, mark PAYMENT_FAILED |
| Payment succeeds but DB update fails | Retry booking confirmation using idempotent recovery job |
| Notification fails | Retry through queue; booking remains confirmed |
| Service crashes mid-flow | Resume using booking state machine |
Payment requests must include idempotency keys.
payment_idempotency_key = booking_id + payment_attempt_number
Payment table:
CREATE TABLE payments (
payment_id BIGINT PRIMARY KEY,
booking_id BIGINT,
provider_payment_id VARCHAR(128),
amount DECIMAL,
currency VARCHAR(8),
status VARCHAR(32),
idempotency_key VARCHAR(128),
created_at TIMESTAMP,
updated_at TIMESTAMP,
UNIQUE(idempotency_key)
);
Use:
Cancellation is also idempotent.
1.User sends cancel request with idempotency key
2.Booking Service verifies booking is cancellable
3.Booking status changes to CANCELLATION_PENDING
4.Inventory is released
5.Refund is initiated
6.Booking status changes to CANCELLED
7.Notification event is published
Cancellation policy can depend on:
Modification is equivalent to a mini rebooking.
Safe approach:
Important:
MODIFICATION_PENDINGMODIFICATION_CONFIRMEDMODIFICATION_FAILEDCREATE TABLE reviews (
review_id BIGINT PRIMARY KEY,
hotel_id BIGINT,
booking_id BIGINT,
user_id BIGINT,
rating INT,
comment TEXT,
status VARCHAR(32),
created_at TIMESTAMP,
UNIQUE(booking_id)
);
Rating aggregates can be updated asynchronously:
ReviewCreated event -> Aggregation Worker -> hotel_rating_summary
This avoids expensive aggregation queries on the hot path.
Hosts can:
When a host updates a property:
Hotel DB update -> HotelUpdated event -> Search Index update
Search index updates are eventually consistent.
Recommendation signals:
Recommendation can be served by:
For interview scope:
Rate limiting protects the platform from abuse and traffic spikes.
Example limits:
Search: 60 requests / minute / user
Availability check: 30 requests / minute / user
Booking creation: 5 requests / minute / user
Review creation: 10 requests / hour / user
Host inventory updates: 100 requests / minute / host
Use Redis token bucket:
key = rate_limit:{user_id}:{api_name}
If exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Idempotency is required for:
CREATE TABLE idempotency_keys (
idempotency_key VARCHAR(128),
user_id BIGINT,
request_hash VARCHAR(256),
response_body JSON,
status_code INT,
created_at TIMESTAMP,
PRIMARY KEY (user_id, idempotency_key)
);
When duplicate request arrives:
409 Conflict.This handles client retries safely.
A popular hotel has only one room left during a major event. Thousands of users try to book it at the same time.
Only one request can successfully hold the final room.
For availability checks, identical requests can be coalesced:
hotel_id + room_type_id + date_range
Multiple users share the same cached availability result for a very short TTL.
Use a 1 to 5 second cache for hot availability reads.
Important:
For extreme contention:
Limit repeated booking attempts per user and IP.
When downstream systems are overloaded:
| Data Cache TTL | |
| Hotel details | 5 to 30 minutes |
| Hotel photos | CDN |
| Reviews summary | 5 to 15 minutes |
| Search results | 30 to 120 seconds |
| Availability hints | 1 to 120 seconds |
| User recommendations | 5 to 30 minutes |
Partition by:
hotel_id
Partition by:
hotel_id or region + hotel_id
This keeps inventory updates for a hotel localized.
Partition by:
user_id or booking_id
For hotel manager views, maintain secondary index by:
hotel_id + check_in_date
Partition by:
hotel_id
This supports fast hotel review lookup.
Important events:
HotelCreated
HotelUpdated
InventoryUpdated
BookingCreated
BookingConfirmed
BookingCancelled
PaymentAuthorized
PaymentFailed
ReviewCreated
Consumers:
Use Kafka / Pulsar with:
Track:
Use structured logs:
{
"request_id": "req_123",
"user_id": "u_123",
"booking_id": "b_789",
"hotel_id": "h_123",
"status": "PAYMENT_AUTHORIZED"
}
Distributed tracing across:
API Gateway -> Booking Service -> Availability Service -> Payment Service -> Notification Service
Deploy services across multiple availability zones.
If Recommendation Service is down:
If Review Service is down:
If Search Index is stale:
If Notification Service is down:
If Payment Provider is down:
Inventory booking requires strong consistency. During a partition, the system should reject bookings rather than risk double-booking.
Search index is eventually consistent. This is acceptable because final booking validates availability.
Keep booking confirmation synchronous for user confidence.
Move non-critical work async:
Room-type-level inventory is simpler and scalable.
Room-level assignment can happen closer to check-in.
The system uses a read-optimized search architecture combined with a strongly consistent booking and inventory core.