Machine Coding Problem

Hotel Booking System

maco30maco60macoAllcommercestrategy-refund-&-fine-grained-concurrent-booking
Commonly Asked By:Booking.comAirbnbExpedia

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: Room registers individual types and prices.
  • Overlapping Reservations: Reservation houses 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):Reservation is solely responsible for checking overlaps. HotelBookingService registers inventory.

Core Execution Workflows

Room Booking Pipeline

  1. Caller requests search: findAvailableRooms(checkIn, checkOut, type).
  2. Retrieve all rooms matching active type.
  3. Filter out rooms that have overlapping reservations in [checkIn, checkOut).
  4. Caller requests booking: bookRoom(guest, roomId, checkIn, checkOut).
  5. Acquire fine-grained room lock.
  6. Re-evaluate overlap safety on target room (preventing check-to-act race conditions).
  7. Deduct dynamic fees, create Reservation instance, 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 cancelled as volatile in 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
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...