Requirements & System Scope
Functional Scope (In-Scope)
- Room Search & Filters: Fetch available room items matching active date ranges and type selections.
- Overlap Interval Checker: Accurately verify check-in check-out ranges to avoid booking collisions.
- Concurrent Booking Prevents: Optimistically or pessimistically lock rooms during reservation pipelines using fine-grained locks to prevent double-booking.
- Structured Cancellation Strategy: Tiered cancellation refund strategies matching flexible vs non-refundable structures.
Explicit Boundaries (Out-of-Scope)
- No Hardware Access Key Integration: Ignores physical card key printing interfaces or lock hardware.
- No Dynamic flight/cab Combos: Strictly manages room logs rather than third-party inventory.
Class Diagram & Entity Relationships
Structural layout showing relationships between hotel rooms and bookings:
Loading...
- Polymorphic Room Profiles:
Roomregisters individual types and prices. - Overlapping Reservations:
Reservationhouses guest profiles, check-in indices, and overlap calculations.
Design Patterns & SOLID Principles
- Strategy Pattern (Cancellation Policy): Decouples customizable refund logic (flexible tiered rates vs non-refundable locked structures) from reservation logs, adhering to Open-Closed Principle (OCP).
- Interval Evaluation Pattern (Overlap Checks): Encapsulating overlap formulas (
existingCheckIn < newCheckOut && newCheckIn < existingCheckOut) inside entity definitions simplifies checks and adheres to SOLID SRP. - Single Responsibility Principle (SRP):
Reservationis solely responsible for checking overlaps.HotelBookingServiceregisters inventory.
Core Execution Workflows
Room Booking Pipeline
- Caller requests search:
findAvailableRooms(checkIn, checkOut, type). - Retrieve all rooms matching active type.
- Filter out rooms that have overlapping reservations in
[checkIn, checkOut). - Caller requests booking:
bookRoom(guest, roomId, checkIn, checkOut). - Acquire fine-grained room lock.
- Re-evaluate overlap safety on target room (preventing check-to-act race conditions).
- Deduct dynamic fees, create
Reservationinstance, release lock, and return.
Concurrency & Thread Safety Strategy
When two guests try to book the final available suite for the exact same weekend simultaneously, race conditions can cause double-booking or double-refund cancellation vulnerabilities:
- Fine-Grained Room Locks: Acquire locks dedicated per room instead of system-wide global block monitors, which maximizes multi-core throughput and avoids performance bottlenecks.
- Double-Checked Lock Guard for Cancellations: Wrap cancellation validations and check-in refund processing inside active locking blocks to prevent concurrent thread double-refunding races.
- Thread-Safe Visibility (Volatile): Declare properties like
cancelledasvolatilein Java to guarantee instant memory visibility across thread caches.
Complete Clean Code Blueprint
Practical reference designs showing booking security in Java and Python:
// โโโ JAVA BLUEPRINT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
enum RoomType { SINGLE, DOUBLE, SUITE }
interface CancellationStrategy {
double calculateRefund(double paidAmount, long checkInTime, long cancelTime);
}
class FlexibleCancellationStrategy implements CancellationStrategy {
@Override
public double calculateRefund(double paidAmount, long checkInTime, long cancelTime) {
long hoursToCheckIn = (checkInTime - cancelTime) / (1000 * 60 * 60);
if (hoursToCheckIn >= 48) {
return paidAmount; // 100% refund
} else if (hoursToCheckIn >= 24) {
return paidAmount * 0.5; // 50% refund
}
return 0.0; // No refund
}
}
class NonRefundableCancellationStrategy implements CancellationStrategy {
@Override
public double calculateRefund(double paidAmount, long checkInTime, long cancelTime) {
return 0.0; // Always 0% refund
}
}
class Room {
private final String id;
private final RoomType type;
private final double basePrice;
private final ReentrantLock lock = new ReentrantLock();
public Room(String id, RoomType type, double basePrice) {
this.id = id;
this.type = type;
this.basePrice = basePrice;
}
public String getId() { return id; }
public RoomType getType() { return type; }
public double getBasePrice() { return basePrice; }
public ReentrantLock getLock() { return lock; }
}
class Reservation {
private final String id;
private final String guestName;
private final Room room;
private final long checkIn;
private final long checkOut;
private final double cost;
private volatile boolean cancelled = false;
public Reservation(String id, String guest, Room room, long in, long out, double cost) {
this.id = id;
this.guestName = guest;
this.room = room;
this.checkIn = in;
this.checkOut = out;
this.cost = cost;
}
public String getId() { return id; }
public Room getRoom() { return room; }
public long getCheckIn() { return checkIn; }
public long getCheckOut() { return checkOut; }
public double getCost() { return cost; }
public boolean isCancelled() { return cancelled; }
public void cancel() { this.cancelled = true; }
public boolean overlaps(long start, long end) {
return !cancelled && checkIn < end && start < checkOut;
}
}
class HotelBookingService {
private final Map<String, Room> rooms = new ConcurrentHashMap<>();
private final List<Reservation> reservations = new CopyOnWriteArrayList<>();
private final CancellationStrategy cancellationStrategy;
public HotelBookingService(CancellationStrategy cancellationStrategy) {
this.cancellationStrategy = cancellationStrategy;
}
public void addRoom(Room r) { rooms.put(r.getId(), r); }
public List<Room> findAvailableRooms(long checkIn, long checkOut, RoomType type) {
List<Room> available = new ArrayList<>();
for (Room room : rooms.values()) {
if (room.getType() != type) continue;
boolean isFree = true;
for (Reservation res : reservations) {
if (res.getRoom().getId().equals(room.getId()) && res.overlaps(checkIn, checkOut)) {
isFree = false;
break;
}
}
if (isFree) {
available.add(room);
}
}
return available;
}
public Reservation bookRoom(String guest, String roomId, long checkIn, long checkOut) {
Room room = rooms.get(roomId);
if (room == null) return null;
// Fine-grained lock per room ensures high throughput compared to global locking
room.getLock().lock();
try {
// Double check availability (Check-then-Act)
for (Reservation res : reservations) {
if (res.getRoom().getId().equals(roomId) && res.overlaps(checkIn, checkOut)) {
System.out.println("[Booking Error] Room " + roomId + " is already booked for these dates!");
return null;
}
}
long days = Math.max(1, (checkOut - checkIn) / (1000 * 60 * 60 * 24));
double totalCost = days * room.getBasePrice();
String resId = "RES-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
Reservation reservation = new Reservation(resId, guest, room, checkIn, checkOut, totalCost);
reservations.add(reservation);
System.out.println("[Booking Success] Guest " + guest + " booked room " + roomId + " (Cost: $" + totalCost + ")");
return reservation;
} finally {
room.getLock().unlock();
}
}
public double cancelReservation(String reservationId, long cancelTime) {
for (Reservation res : reservations) {
if (res.getId().equals(reservationId)) {
res.getRoom().getLock().lock();
try {
if (res.isCancelled()) {
System.out.println("[Cancellation Error] Reservation already cancelled.");
return 0.0;
}
res.cancel();
double refund = cancellationStrategy.calculateRefund(res.getCost(), res.getCheckIn(), cancelTime);
System.out.println("[Cancellation Success] Refund processed: $" + refund + " for reservation " + reservationId);
return refund;
} finally {
res.getRoom().getLock().unlock();
}
}
}
System.out.println("[Cancellation Error] Reservation not found.");
return 0.0;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== HOTEL CONCURRENT SIMULATION DRIVER ===");
CancellationStrategy strategy = new FlexibleCancellationStrategy();
HotelBookingService hotel = new HotelBookingService(strategy);
Room r1 = new Room("SUITE-101", RoomType.SUITE, 250.0);
hotel.addRoom(r1);
long now = System.currentTimeMillis();
long in = now + (3 * 24 * 60 * 60 * 1000); // 3 days from now
long out = now + (5 * 24 * 60 * 60 * 1000); // 5 days from now
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// Spawn 2 parallel concurrent booking attempts for the SAME room
threadPool.submit(() -> hotel.bookRoom("Alice", "SUITE-101", in, out));
threadPool.submit(() -> hotel.bookRoom("Bob", "SUITE-101", in, out));
threadPool.shutdown();
threadPool.awaitTermination(1, TimeUnit.SECONDS);
}
}
Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...