Hotel Booking System (OYO / Booking.com) Low Level Design is a popular problem in product-based SDE interviews. It covers room availability management, dynamic pricing, concurrent reservation safety, and cancellation policy. Frequently asked at OYO, MakeMyTrip, and Expedia, this guide covers the complete Hotel Booking LLD with Java code, class diagram, and FAQ.
Why Interviewers Ask Hotel Booking LLD
The hotel booking problem is a nuanced variation of the movie ticket booking problem. Interviewers want to see:
- Can you model room availability across date ranges (not just timeslots)?
- Do you use Strategy pattern for dynamic and seasonal pricing?
- Can you prevent double-booking with optimistic locking or row-level locks?
- Do you design cancellation policy as a first-class concept?
- Can you model Hotel, Room, RoomType, and Reservation as distinct entities?
Functional Requirements
- Users can search hotels by city, check-in date, check-out date, and guests
- Each hotel has multiple room types (Standard, Deluxe, Suite) with different prices
- Users can book a room — system reserves it for the requested date range
- Booking confirmation after payment; cancellation within the cancellation window
- Cancellation policy: free within 24 hours, 50% refund within 7 days, no refund after
- Hotel managers can update room availability and prices
- Admin can view all reservations and revenue per hotel
Non-Functional Requirements
- No two users can book the same room for overlapping dates
- Room availability queries must be fast — indexed by date range
- Adding a new pricing strategy (e.g., loyalty discount) must not change booking logic
- Cancellation refund calculation must be deterministic and auditable
Core Entities — Hotel Booking LLD Class Design
- Hotel — id, name, city, starRating, amenities, rooms
- RoomType — STANDARD / DELUXE / SUITE — defines base price and capacity
- Room — id, hotel, roomNumber, roomType, floor, amenities
- Reservation — id, user, room, checkIn, checkOut, totalPrice, status
- PricingStrategy — interface; BasePricing, SeasonalPricing, LoyaltyPricing
- CancellationPolicy — interface; FreeWithin24h, TieredRefundPolicy
- Payment — reservationId, amount, method, status, refundAmount
- RoomAvailabilityService — checks and reserves date ranges
Text-Based Class Diagram
Hotel +-- id, name, city: String +-- starRating: int +-- rooms: List<Room> RoomType (enum) +-- STANDARD, DELUXE, SUITE +-- basePrice: double +-- maxGuests: int Room +-- id, roomNumber: String +-- hotel: Hotel +-- roomType: RoomType +-- floor: int Reservation +-- id, user: User, room: Room +-- checkIn, checkOut: LocalDate +-- totalPrice: double +-- status: ReservationStatus (PENDING/CONFIRMED/CANCELLED) +-- paymentId: String PricingStrategy (interface) +-- calculatePrice(room, checkIn, checkOut): double BasePricing implements PricingStrategy SeasonalPricing implements PricingStrategy LoyaltyPricing implements PricingStrategy CancellationPolicy (interface) +-- calculateRefund(reservation, cancelledAt): double TieredRefundPolicy implements CancellationPolicy
Pricing Strategies — Java
public interface PricingStrategy {
double calculatePrice(Room room, LocalDate checkIn, LocalDate checkOut);
}
public class BasePricing implements PricingStrategy {
@Override
public double calculatePrice(Room room, LocalDate checkIn, LocalDate checkOut) {
long nights = ChronoUnit.DAYS.between(checkIn, checkOut);
return room.getRoomType().getBasePrice() * nights;
}
}
public class SeasonalPricing implements PricingStrategy {
private final PricingStrategy base = new BasePricing();
@Override
public double calculatePrice(Room room, LocalDate checkIn, LocalDate checkOut) {
double basePrice = base.calculatePrice(room, checkIn, checkOut);
double multiplier = isSeason(checkIn) ? 1.5 : 1.0;
return basePrice * multiplier;
}
private boolean isSeason(LocalDate date) {
int month = date.getMonthValue();
return month == 12 || month == 1 || (month >= 6 && month <= 8); // winter + summer peak
}
}
public class LoyaltyPricing implements PricingStrategy {
private final PricingStrategy base;
private final double discountRate;
public LoyaltyPricing(PricingStrategy base, double discountRate) {
this.base = base;
this.discountRate = discountRate; // e.g., 0.10 for 10% off
}
@Override
public double calculatePrice(Room room, LocalDate checkIn, LocalDate checkOut) {
return base.calculatePrice(room, checkIn, checkOut) * (1 - discountRate);
}
}Room Availability and Concurrent Reservation Safety
public class RoomAvailabilityService {
private final ReservationRepository reservationRepo;
public List<Room> findAvailableRooms(String city, LocalDate checkIn, LocalDate checkOut, RoomType type) {
List<Hotel> hotels = hotelRepo.findByCity(city);
return hotels.stream()
.flatMap(h -> h.getRooms().stream())
.filter(r -> r.getRoomType() == type)
.filter(r -> isAvailable(r.getId(), checkIn, checkOut))
.collect(Collectors.toList());
}
public boolean isAvailable(String roomId, LocalDate checkIn, LocalDate checkOut) {
// Check for overlapping CONFIRMED reservations
// Overlap condition: existing.checkIn < requested.checkOut AND existing.checkOut > requested.checkIn
return reservationRepo.countOverlapping(roomId, checkIn, checkOut, ReservationStatus.CONFIRMED) == 0;
}
// Atomic check-and-reserve with optimistic locking
public Reservation reserve(String roomId, String userId, LocalDate checkIn, LocalDate checkOut,
double price) {
// Use DB-level constraint: unique(roomId, checkIn, status=CONFIRMED)
// Or: SELECT FOR UPDATE on overlapping rows
if (!isAvailable(roomId, checkIn, checkOut))
throw new RoomNotAvailableException("Room already booked for these dates");
Reservation res = new Reservation(UUID.randomUUID().toString(),
userRepo.findById(userId), roomRepo.findById(roomId),
checkIn, checkOut, price, ReservationStatus.PENDING);
return reservationRepo.save(res); // DB constraint catches race conditions
}
}Cancellation Policy
public interface CancellationPolicy {
double calculateRefund(Reservation reservation, LocalDateTime cancelledAt);
}
public class TieredRefundPolicy implements CancellationPolicy {
@Override
public double calculateRefund(Reservation reservation, LocalDateTime cancelledAt) {
long hoursBeforeCheckIn = ChronoUnit.HOURS.between(cancelledAt,
reservation.getCheckIn().atStartOfDay());
if (hoursBeforeCheckIn >= 24 * 7) return reservation.getTotalPrice(); // full refund > 7 days
if (hoursBeforeCheckIn >= 24) return reservation.getTotalPrice() * 0.5; // 50% within 7 days
return 0.0; // no refund within 24 hours
}
}Key Design Decisions
- Date range availability query: The overlap condition is: existing.checkIn is before requested.checkOut AND existing.checkOut is after requested.checkIn. This single predicate covers all overlap cases — partial, full containment, and identical dates.
- DB constraint as the last defense against double-booking: Application-level availability checks are subject to TOCTOU races. A database partial unique index on (roomId, status=CONFIRMED) with an overlap exclusion constraint (available in PostgreSQL) is the correct safety net.
- Cancellation policy as Strategy: Different room types or booking channels may have different cancellation terms. Storing the policy name on the Reservation at booking time ensures the correct policy is applied even if the default policy changes later.
- Decorator pattern for pricing: LoyaltyPricing wraps any base pricing strategy and applies a discount. SeasonalPricing can wrap BasePricing. Pricing policies compose without modifying existing classes.
Common Follow-Up Questions
- "How do you handle check-in time vs check-out time within the same day?" — Add checkInTime and checkOutTime to Reservation. Default check-in: 2pm, check-out: 11am. A room booked from Day 1 to Day 3 check-out is available again from Day 3 2pm onwards.
- "How do you support multi-room bookings for a group?" — A Booking entity groups multiple Reservations. Payment is made at the Booking level. Cancellation cancels all reservations in the booking atomically.
- "How do you update room prices for future bookings without affecting existing ones?"— Store totalPrice on the Reservation at booking time. Price changes affect future bookings only. Historical prices are immutable — preserved in the Reservation record.
FAQ — Hotel Booking System Low Level Design
What design patterns are used in Hotel Booking LLD?
The primary patterns are Strategy (PricingStrategy — Base, Seasonal, Loyalty),Decorator (LoyaltyPricing wraps other pricing strategies),Template Method (booking flow: search → select → pay → confirm), andState Machine (Reservation status: PENDING → CONFIRMED → CANCELLED).
How do you prevent double-booking in a hotel system?
At the application layer: check for overlapping CONFIRMED reservations before creating a new one. At the database layer: use a partial unique index or exclusion constraint (PostgreSQL tsrange with EXCLUDE) on (roomId, dateRange) where status=CONFIRMED. The DB constraint is the authoritative guard — the application check is an optimization to give users a better error message.
How do you model room availability across date ranges?
Query reservations where (checkIn is before requested checkOut) AND (checkOut is after requested checkIn). Any row matching this condition means the room is unavailable. Index on roomId and checkIn for query performance. Store availability as an in-memory calendar per room for sub-millisecond lookups in high-traffic scenarios.
How do you handle hotel cancellation policies?
Model CancellationPolicy as a Strategy interface with calculateRefund(reservation, cancelAt). Record the policy name on the Reservation at booking time (not a foreign key — the policy text may change). On cancellation, instantiate the saved policy and compute the refund. This ensures historical reservations always use the policy they were booked under.