Skip to the solution
HardSystem Design9 min read

How would you design a hotel reservation system?

0 views

The problem

Question presented to candidate: "Design the reservation system for a hotel chain like Marriott, or a booking site like Booking.com: guests search for hotels by city and dates, see prices, and book a room type for several nights. Make sure we never sell more rooms than we have, unless we decide to overbook on purpose."

What a strong answer should cover:

  • Inventory modelled as counters per (hotel, room type, date), not individual rooms: guests book a type, the front desk assigns the room number at check-in
  • The double-booking race (two guests, one room left) and how to prevent it: a conditional UPDATE ... WHERE reserved + n <= total, optimistic versioning, or row locks, with the trade-offs
  • A multi-night stay updates several date rows in one transaction, all or nothing
  • The reservation lifecycle (pending, paid, confirmed, cancelled), holds that expire during payment, and an idempotent reserve call
  • Overbooking as a business setting (for example accept 110% of rooms) with the no-show arithmetic behind it
  • Scale: writes are tiny (a few per second), reads are large (search and availability); cache and replicate reads, shard by hotel

Clarifying questions expected:

  • One chain or an aggregator of many hotels? How many hotels and rooms?
  • Do guests book a room type or a specific room?
  • Is overbooking allowed, and by how much? Pay now or pay at the hotel?

Code / implementation expected: Optional โ€” the example reproduces the double-booking race and shows conditional updates, all-or-nothing multi-night bookings, idempotent retries and the overbooking curve.

  • system-design
  • case-study

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read ยท 16 sections

Read the code

Hotel inventory counters: the double-booking race, conditional updates, all-or-nothing multi-night stays, retries and overbooking
// Hotel inventory: per (hotel, room type, date) counters, the double-booking race,
// all-or-nothing multi-night stays, idempotent retries and the overbooking trade-off.
console.log("== 1. Capacity (assumptions) ==");
const rooms = 1000000, occupancy = 0.7, avgNights = 3;
const perDay = Math.round((rooms * occupancy) / avgNights);
const tps = perDay / 86400;
console.log("reservations/day = 1,000,000 x 0.7 / 3 nights = " + perDay);
console.log("reservations/s = " + tps.toFixed(2) + ", booking page QPS ~ " + (tps * 10).toFixed(1) + ", room detail QPS ~ " + (tps * 100).toFixed(0));
console.log("inventory rows = 5,000 hotels x 20 room types x 730 days = " + 5000 * 20 * 730);

// inventory table: key "hotel|type|date" -> {total, reserved, version}
const inv = new Map();
const key = (d) => "H1|KING|" + d;
const reset = () => { for (const [d, r] of [["12-23", 6], ["12-24", 9], ["12-25", 4]]) inv.set(key(d), { total: 10, reserved: r, version: 0 }); };
const show = () => ["12-23", "12-24", "12-25"].map((d) => d + "=" + inv.get(key(d)).reserved + "/10").join("  ");

console.log("\n== 2. Two guests, one room left on 12-24 ==");
reset();
let row = inv.get(key("12-24"));
const rows = [];
for (let i = 0; i < 9; i++) rows.push("old-" + i); // 9 existing reservation rows
const seen1 = rows.length, seen2 = rows.length; // both SELECT count(*) before either INSERTs
if (seen1 < row.total) rows.push("guest-A");
if (seen2 < row.total) rows.push("guest-B");
console.log("naive SELECT count then INSERT: both saw " + seen1 + " of 10, rows now " + rows.length + " -> DOUBLE BOOKED");
reset();
row = inv.get(key("12-24"));
const condUpdate = (r, n) => { if (r.reserved + n <= r.total) { r.reserved += n; r.version++; return 1; } return 0; };
console.log("conditional UPDATE ... WHERE reserved + 1 <= total: T1 rows=" + condUpdate(row, 1) + ", T2 rows=" + condUpdate(row, 1) + ", reserved = " + row.reserved);
reset();
row = inv.get(key("12-24"));
const v1 = row.version, v2 = row.version;
const casUpdate = (r, ver) => { if (r.version !== ver) return "version conflict"; if (r.reserved + 1 > r.total) return "sold out"; r.reserved++; r.version++; return "booked"; };
console.log("optimistic version check: T1 " + casUpdate(row, v1) + ", T2 " + casUpdate(row, v2) + ", T2 retry " + casUpdate(row, row.version));

console.log("\n== 3. A 3-night stay (12-23 to 12-26) when 12-24 is full ==");
reset();
inv.get(key("12-24")).reserved = 10;
const nights = ["12-23", "12-24", "12-25"];
console.log("before:        " + show());
for (const d of nights) { if (!condUpdate(inv.get(key(d)), 1)) break; }
console.log("night by night: " + show() + "   <- 12-23 leaked a room");
reset();
inv.get(key("12-24")).reserved = 10;
function bookStay(ds, n) { // one transaction: check every night, then apply all
  if (!ds.every((d) => inv.get(key(d)).reserved + n <= inv.get(key(d)).total)) return false;
  for (const d of ds) condUpdate(inv.get(key(d)), n);
  return true;
}
console.log("one transaction: booked=" + (bookStay(nights, 1) ? "yes" : "no") + "  " + show());
inv.get(key("12-24")).reserved = 9;
console.log("after a cancel:  booked=" + (bookStay(nights, 1) ? "yes" : "no") + "  " + show());

console.log("\n== 4. Retry with the same reservation id ==");
reset();
const reservations = new Map();
function reserve(resId, ds) {
  if (reservations.has(resId)) return "existing " + resId;
  if (!bookStay(ds, 1)) return "sold out";
  reservations.set(resId, ds);
  return "created " + resId;
}
console.log(reserve("R-7f3a", nights) + " | timeout, client retries: " + reserve("R-7f3a", nights) + " | " + show());

console.log("\n== 5. Overbooking 100 rooms, 5% no-shows, room 200, walking a guest costs 400 ==");
function outcome(booked, pShow) {
  let walks = 0, filled = 0;
  for (let k = 0; k <= booked; k++) {
    let c = 1; // binomial coefficient C(booked, k) as a float
    for (let i = 0; i < k; i++) c = (c * (booked - i)) / (i + 1);
    const p = c * Math.pow(pShow, k) * Math.pow(1 - pShow, booked - k);
    walks += p * Math.max(0, k - 100);
    filled += p * Math.min(k, 100);
  }
  return [filled, walks];
}
for (const n of [100, 102, 104, 106, 108, 110]) {
  const [filled, walks] = outcome(n, 0.95);
  const net = 200 * filled - 400 * walks;
  console.log("  accept " + n + ": rooms filled " + filled.toFixed(2) + ", guests walked " + walks.toFixed(2) + ", expected net " + net.toFixed(0));
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 97 of 106 in System design

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextHard

How would you design ad click event aggregation?

Continue