Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
🎯 Problem Statement
Design a hotel booking system that can:- Manage multiple hotels with different room types
- Handle room reservations and cancellations
- Process check-in and check-out
- Calculate pricing with dynamic rates and discounts
- Handle concurrent booking requests
📋 Step 1: Clarify Requirements
Questions to Ask the Interviewer
| Category | Question | Impact on Design |
|---|---|---|
| Scale | Single hotel or chain/platform? | Multi-tenancy design |
| Rooms | Different room types? Amenities? | Room class hierarchy |
| Pricing | Dynamic pricing? Seasonal rates? | Strategy pattern for pricing |
| Booking | Instant booking or request-based? | Confirmation flow |
| Payment | Full upfront or pay at hotel? | Payment integration |
| Concurrency | Handle simultaneous bookings? | Locking strategy |
Functional Requirements
- Search available rooms by date, location, room type
- Book rooms with guest information
- Cancel/modify reservations
- Check-in and check-out guests
- Calculate total bill including extras
- Send booking confirmations
Non-Functional Requirements
- Handle concurrent bookings (no double-booking!)
- Support multiple payment methods
- Maintain booking history
- Real-time availability updates
🧩 Step 2: Identify Core Objects
Hotel Structure
Booking
Services
Entity-Responsibility Mapping
| Entity | Responsibilities | Pattern |
|---|---|---|
Hotel | Manage rooms, handle searches | - |
Room | Track availability, calculate pricing | - |
Reservation | Manage booking lifecycle | State Pattern |
PricingStrategy | Calculate room rates | Strategy Pattern |
NotificationService | Send confirmations | Observer Pattern |
PaymentProcessor | Handle payments | Strategy Pattern |
Reservation State Machine
📐 Step 3: Class Diagram
Step 4: Implementation
Enums and Constants
Guest Class
Room Classes
Pricing Strategy
Bill and Charges
Reservation Class
Hotel Class
Step 5: Usage Example
Key Design Decisions
Why Strategy Pattern for Pricing?
Why Strategy Pattern for Pricing?
calculate_price() method with dozens of conditional branches that grow every time a new pricing rule is added. With Strategy, each pricing model is encapsulated in its own class, and the Reservation receives the appropriate strategy at creation time. This means the revenue team can add a “last-minute deal” pricing model without touching any existing pricing code — a textbook example of the Open/Closed Principle. In an interview, pointing out that the pricing strategy can be composed (e.g., DynamicPricing wrapping SeasonalPricing wrapping StandardPricing) shows you understand the Decorator pattern as well.Why Thread Locking on Room Availability?
Why Thread Locking on Room Availability?
Why Separate Bill from Reservation?
Why Separate Bill from Reservation?
Why Reservation Status State Machine?
Why Reservation Status State Machine?
Interview Deep-Dive Questions
Q1: Walk me through exactly how the system checks room availability for a date range. What is the time complexity, and what breaks if the hotel has 500 rooms and 10,000 historical reservations?
Q1: Walk me through exactly how the system checks room availability for a date range. What is the time complexity, and what breaks if the hotel has 500 rooms and 10,000 historical reservations?
Room.is_available(check_in, check_out)acquires the room’s lock, then iterates every reservation on that room. For each reservation with status CONFIRMED or CHECKED_IN, it checks for date overlap using the condition:not (check_out <= res.check_in_date or check_in >= res.check_out_date). If any overlap exists, the room is unavailable. This is O(R) per room where R is the number of reservations for that room.Hotel.search_available_rooms()callsis_available()for each room, making the total cost O(N * R) where N is rooms and R is average reservations per room. With 500 rooms and 20 reservations each, that is 10,000 comparisons — fine. But_reservationsis a flat list that never gets cleaned up. After a year of operation with 10,000 historical reservations per room (checked-out, cancelled, no-shows all still in the list), you are scanning 5 million entries per search. The fix is straightforward: filter only CONFIRMED/CHECKED_IN reservations before the overlap check, or maintain a separate “active reservations” list that gets cleaned up on checkout.- A production system would not iterate at all — it would use an interval tree or a database query with indexed date ranges. The date-overlap check translates directly to SQL:
WHERE room_id = ? AND status IN ('confirmed','checked_in') AND check_in < ? AND check_out > ?with a composite index on(room_id, status, check_in, check_out). - Example: Booking.com reportedly handles 1.5 million room-nights per day. Their availability engine uses pre-computed availability bitmaps — one bit per room per day. Checking availability for a 3-night stay is three bitwise AND operations, not a linear scan of reservations.
- The per-room
threading.Lockmeans two users searching for available rooms will contend on the same lock if they are checking the same room. Would you use a read-write lock (multiple readers, single writer) instead? What are the tradeoffs? - If the hotel wants to show real-time availability on a website where 1,000 users are searching simultaneously, how would you architect this differently — caching, eventual consistency, or something else?
Q2: Two users simultaneously try to book the last available Deluxe room for the same dates. Trace through the code and explain exactly how double-booking is prevented — and identify any remaining vulnerability.
Q2: Two users simultaneously try to book the last available Deluxe room for the same dates. Trace through the code and explain exactly how double-booking is prevented — and identify any remaining vulnerability.
- The
Hotel.make_reservation()method wraps the entire flow inwith self._lock— a single mutex for the entire hotel. Thread A enters, acquires the lock, callsroom.is_available()which returns True, creates aReservation, and callsreservation.confirm()which callsroom.add_reservation(). The reservation is appended to the room’s list and status becomes CONFIRMED. Thread A releases the lock. Thread B enters, acquires the lock, callsroom.is_available()— this time it finds Thread A’s confirmed reservation overlapping the dates and returns False.make_reservationreturnsNone. Double-booking prevented. - The vulnerability is the double-lock acquisition.
Hotel.make_reservationholdsself._lock(hotel-level), then callsroom.is_available()which acquiresroom._lock(room-level). Thenreservation.confirm()callsroom.add_reservation()which also acquiresroom._lock. In Python’sthreading.Lock, this is a non-reentrant lock by default — the second acquisition will deadlock. The code has a deadlock bug. Theroom.is_available()call insidehotel.make_reservation()will acquire the room lock, but thenroom.add_reservation()tries to acquire it again from the same thread, causing a hang. - The fix is either: (a) use
threading.RLock(reentrant lock) on the room, (b) remove the room-level lock entirely since the hotel lock already serializes access, or (c) restructure sois_availableandadd_reservationshare a single lock-acquisition scope. - Example: This is a real-world class of bugs. I have seen a hotel booking system deadlock in production because the availability check and the booking confirmation each acquired the same database row lock. The system appeared to “hang” — no error, no timeout, just stuck threads. It took 3 hours to diagnose because the symptoms looked like a slow database.
- If this were a distributed system with multiple server instances behind a load balancer, the in-process
threading.Lockis useless. How would you implement distributed locking — RedisSETNX, databaseSELECT FOR UPDATE, or an optimistic concurrency approach with version numbers? - Some hotel booking systems use a “temporary hold” pattern — the room is locked for 10 minutes while the user enters payment details. How would you implement a time-limited lock that auto-releases if the user abandons checkout?
Q3: The cancellation policy has three tiers: flexible, moderate, and non-refundable. Critique the current implementation and redesign it for a real hotel system.
Q3: The cancellation policy has three tiers: flexible, moderate, and non-refundable. Critique the current implementation and redesign it for a real hotel system.
- The current implementation encodes all three policies inside
Reservation.cancel()as if/elif branches with hardcoded day thresholds (7 days = full refund, 3 days = 50%, else = nothing for flexible). This has several problems: it violates OCP (adding a fourth policy means editing the cancel method), it puts business logic in the domain entity (the Reservation should not know about refund percentages — those are hotel policy, not reservation state), and the thresholds are hardcoded (no way for different room types or seasons to have different cancellation windows). - A cleaner design uses the Strategy pattern — the same one already used for pricing. Define a
CancellationPolicyabstract class withcalculate_refund(reservation, cancellation_date) -> Decimal. ImplementFlexibleCancellationPolicy,ModerateCancellationPolicy,NonRefundableCancellationPolicy, andCustomCancellationPolicy. The policy is assigned at booking time and stored on the Reservation.cancel()simply delegates toself.cancellation_policy.calculate_refund(self, date.today()). - Real hotel systems make this even more nuanced: the refund might be prorated (cancel 5 days before = charge for 1 night, refund the rest), the cancellation fee might differ by booking channel (direct booking = lenient, third-party = strict), and some policies have a “free cancellation window” (cancel within 24 hours of booking for full refund, regardless of check-in date).
- Example: Airbnb has 5 cancellation tiers (Flexible, Moderate, Strict, Super Strict 30, Super Strict 60) each with different day thresholds, refund percentages, and service fee rules. Encoding all of those as if/elif branches in a single method would be unmaintainable. Strategy objects make each policy self-contained and independently testable.
- A guest books a non-refundable room but then the hotel overbooks and cannot honor the reservation. Who gets the refund, and how does the system handle forced cancellations that override the policy? Where does this exception logic live?
- The
refund_policyis passed as a string parameter tocancel(). What happens if the caller passes “flexibl” (a typo)? How would you make the policy selection type-safe and impossible to misconfigure?
Q4: The pricing system uses Strategy pattern with StandardPricing, SeasonalPricing, WeekendPricing, and DynamicPricing. A real hotel needs ALL of these simultaneously. How would you compose them?
Q4: The pricing system uses Strategy pattern with StandardPricing, SeasonalPricing, WeekendPricing, and DynamicPricing. A real hotel needs ALL of these simultaneously. How would you compose them?
- The current design lets you pick one strategy per reservation, but real pricing is layered: the base rate is modified by season, then by day-of-week, then by occupancy, then by loyalty tier, then by booking channel. You need a Decorator pattern or a chain of pricing modifiers. Each modifier wraps the previous one and applies its adjustment.
- Concretely:
PricingStrategystays as the base interface.CompositePricingStrategytakes a base strategy and a list ofPriceModifierobjects. Each modifier hasadjust_price(base_price, date) -> Decimal. The composite iterates the modifiers in order:price = basethen for each modifierprice = modifier.adjust(price, date). Seasonal modifier multiplies by 1.3 in summer. Weekend modifier multiplies by 1.2 on Saturdays. Occupancy modifier multiplies by 1.15 when occupancy is above 80%. - The order of application matters and can produce different results. If base is 156. Weekend 1.2x then seasonal 1.3x = 20 surcharge) and another is multiplicative (1.3x), order matters: 20 = 156 vs 130 + 150. You need clear documentation of modifier ordering.
- Example: The
DynamicPricingclass in the code actually already tries to combine seasonal + weekend + occupancy in a single class. But it is not composable — you cannot reuse the seasonal logic independently. Splitting it into three separate modifiers that compose via the Decorator pattern gives you the same result with better reuse.
DynamicPricing hardcodes the combination. What if a corporate rate should ignore weekend surcharges but keep seasonal adjustments? You cannot configure DynamicPricing for that without editing the class.Follow-ups:- Hotels often run promotions: “20% off for bookings made 30 days in advance” or “third night free.” These are not per-night multipliers — they modify the total. How would you extend the modifier chain to support both per-night and per-stay adjustments?
- If two conflicting promotions apply (loyalty discount 15% + early booking discount 20%), should they stack (35% off), compound (1 - 0.85 * 0.80 = 32% off), or should the system pick the better one? How would you make this configurable per hotel?
Q5: The Reservation class has a state machine: Pending, Confirmed, Checked-In, Checked-Out, Cancelled, No-Show. What invalid state transitions could occur, and how does the code prevent (or fail to prevent) them?
Q5: The Reservation class has a state machine: Pending, Confirmed, Checked-In, Checked-Out, Cancelled, No-Show. What invalid state transitions could occur, and how does the code prevent (or fail to prevent) them?
- Valid transitions defined in the diagram: Pending to Confirmed (via
confirm()), Confirmed to Checked-In (viacheck_in()), Checked-In to Checked-Out (viacheck_out()), Pending/Confirmed to Cancelled (viacancel()), Confirmed to No-Show (via timeout). Each method checks the current status before proceeding. - Transitions the code prevents:
check_in()checksstatus != CONFIRMEDand rejects if not.check_out()checksstatus != CHECKED_IN.cancel()checks for CHECKED_IN or CHECKED_OUT and rejects those. These guards prevent the most obvious invalid transitions. - Transitions the code does NOT prevent: There is no guard against calling
confirm()on an already-confirmed reservation — it only checksstatus != PENDING, so callingconfirm()on a CANCELLED reservation would pass if the status were somehow set back to PENDING. More critically, there is no protection againstcancel()on a PENDING reservation that was already cancelled — callingcancel()twice would recalculate the refund both times. TheNo-Showtransition has no implementation at all — nothing in the code sets this status. - A robust state machine implementation would use an explicit transition table: a dictionary mapping
(current_state, event)to(next_state, action). Any transition not in the table raisesInvalidTransitionError. This makes illegal transitions impossible by construction rather than relying on each method having the right guard. - Example: In a real hotel PMS (Property Management System), the state machine includes even more states: Waitlisted, Guaranteed, Non-Guaranteed, Partially-Checked-In (group booking where some guests arrive), and Due-Out. Managing this with per-method if-checks becomes impossible — an explicit state machine library (like
transitionsin Python) is essential.
- The No-Show status has no implementation. How would you detect no-shows (guest did not check in by a cutoff time), and should this be a background cron job, an event-driven trigger, or manual staff action?
- If a guest calls to modify their reservation dates (not cancel), is
modify()a separate state transition or does it stay in the same state? What happens if the new dates are unavailable — does the original reservation remain intact?
Q6: The Bill class accumulates room charges, service charges, and tax. Critique the financial modeling — what would an accountant flag as problematic?
Q6: The Bill class accumulates room charges, service charges, and tax. Critique the financial modeling — what would an accountant flag as problematic?
- Issue 1 — Tax calculation is wrong for real-world use. The code applies a flat 12% tax on the subtotal. In reality, room tax, service tax, and food tax are often different rates. Room charges might have a 12% hotel occupancy tax + 2% city tax + 1% tourism levy. Room service food might have 8% sales tax. Minibar alcohol might have 15% excise tax. A flat rate across all charges is financially incorrect.
- Issue 2 — Payments store amounts as floats. The
add_paymentmethod wraps amount infloat(amount), converting a preciseDecimalto a lossyfloat. After enough transactions,get_balance_due()converts back toDecimal(str(p["amount"]))which introduces floating-point representation errors. A bill with charges of 200.20, 0.0000000001 instead of $0.00 due to float roundtrip. - Issue 3 — No audit trail. Charges can be added but never removed or reversed. If a minibar charge is disputed, there is no
remove_charge()orvoid_charge()method. In accounting, you never delete a charge — you add a negative “adjustment” entry. The Bill class has no concept of credit notes or voids. - Issue 4 — No payment status tracking. A credit card payment might be authorized but not yet captured. The code treats
add_paymentas final. Real hotel PMS systems have authorization holds, partial captures, and refund transactions. - Example: Marriott’s billing system generates an itemized folio with per-line tax breakdowns, differentiating occupancy tax, sales tax, and resort fees. Their system also tracks pre-authorization holds separately from settled payments. The current Bill class could not produce a legally compliant hotel invoice in most jurisdictions.
- The hotel operates in multiple countries with different tax regimes. How would you design a
TaxCalculatorthat applies the correct tax rules by jurisdiction without hardcoding rates in the Bill class? - A guest disputes a charge after checkout. The payment has already been processed. How do you model a partial refund in the billing system while maintaining a clean audit trail?
Q7: Hotels routinely overbook by 5-10% because of expected cancellations and no-shows. How would you add overbooking support to this design, and what happens when the gamble fails?
Q7: Hotels routinely overbook by 5-10% because of expected cancellations and no-shows. How would you add overbooking support to this design, and what happens when the gamble fails?
- Overbooking means
Room.is_available()andHotel.make_reservation()need to consider expected availability, not just current bookings. You would add anoverbooking_ratioconfig (e.g., 1.05 for 5% overbooking).search_available_roomswould return rooms even when all physical rooms are booked, as long as total confirmed reservations do not exceedcapacity * overbooking_ratio. For 100 rooms, you allow up to 105 confirmed reservations. - The implementation requires separating physical room assignment from reservation confirmation. Currently, a reservation is tied to a specific Room object at booking time. With overbooking, reservations should reference a room type, not a specific room. The specific room is assigned at check-in time, not at booking time. This is a fundamental architectural shift — the Reservation class needs a
room_typefield instead of (or in addition to) aroomfield, and a newRoomAssignmentstep at check-in. - When the gamble fails (all guests actually show up), the hotel must “walk” a guest — relocate them to a comparable or upgraded room at a partner hotel, at the original hotel’s expense. This requires a
WalkingPolicythat determines: which guest gets walked (usually the one with the lowest rate or no loyalty status), what compensation they receive (free night, upgrade, cash), and integration with partner hotel APIs. - Example: Airlines pioneered this model — every major airline overbooks by 10-15%, and “involuntary denied boarding” has detailed DOT regulations. Hotels are less regulated but follow similar practices. Hilton’s revenue management system dynamically adjusts the overbooking ratio based on historical no-show rates per day-of-week and season.
- How would you calculate the optimal overbooking ratio? What data inputs would you need (historical no-show rate, cancellation rate, walk cost vs. empty-room cost), and is this a design problem or a data science problem?
- If a walked guest has a confirmed reservation and the hotel cannot provide the room, is this a breach of contract? How should the system differentiate between a “guaranteed” reservation (credit card on file, must honor) and a “non-guaranteed” one (no penalty for no-show, first to be walked)?
Q8: The DynamicPricing strategy uses an occupancy_rate parameter that is set at construction time. In reality, occupancy changes minute by minute. How would you design a pricing system that reacts to real-time demand?
Q8: The DynamicPricing strategy uses an occupancy_rate parameter that is set at construction time. In reality, occupancy changes minute by minute. How would you design a pricing system that reacts to real-time demand?
- The current
DynamicPricingacceptsoccupancy_rateas a constructor argument, making it a snapshot frozen at initialization time. A real dynamic pricing system needs to query current occupancy at price-calculation time. The simplest fix: instead of passing a float, pass a callableoccupancy_provider: Callable[[], float]that queries theHotel.get_occupancy_rate()method on each price calculation. This way the pricing strategy always uses fresh data. - But true dynamic pricing goes far beyond occupancy. Revenue management systems use: competitor pricing (scrape Booking.com and Expedia for comparable hotels), demand signals (local events — a Taylor Swift concert means 3x rates), booking velocity (if rooms are selling faster than expected for a date, raise prices), lead time (bookings 6 months out are discounted, bookings for tonight are premium), and price elasticity (business travelers are price-insensitive Mon-Thu, leisure travelers are elastic Fri-Sun).
- Architecturally, this means the
PricingStrategyinterface needs richer context than justbase_price, check_in, check_out. You would pass aPricingContextobject containing occupancy rate, day of week, lead time (days until check-in), competitor rates, and event calendar. Each pricing modifier examines the context fields it cares about. - Example: IDeaS (a major hotel revenue management vendor) recalculates room rates every few minutes based on demand signals. Their system uses machine learning models trained on years of historical booking data. The architecture separates the pricing model (ML) from the pricing engine (applies the model output to specific reservations) — the same Strategy pattern, but the strategy’s internals are a neural network, not a multiplier table.
- If the pricing system recalculates rates every 5 minutes, a guest might see 220. How do you handle this — honor the displayed price, show an error, or something else?
- Dynamic pricing creates a perverse incentive: a guest who checks availability repeatedly might see prices rise because their searches increase the “demand signal.” How would you prevent your own search traffic from inflating prices?