Skip to the solution
HardSystem Design8 min read

How would you design a ticket booking system like Ticketmaster?

1.2k views

The problem

Question presented to candidate: "Design a ticketing system like Ticketmaster. Most of the time it sells tickets for small shows, but a few times a year a huge artist goes on sale and millions of fans try to buy 70,000 stadium seats in the same few minutes. Each fan picks specific seats, has a few minutes to pay, and no seat can ever be sold twice."

What a strong answer should cover:

  • Why this is harder than hotels: specific seats (not counters), extreme contention at one instant, bots, and a burst of traffic orders of magnitude above normal
  • A virtual waiting room that admits fans at the rate the booking path can handle, with tokens, fairness and bot checks, while static pages come from a CDN
  • Seat holds with a TTL: AVAILABLE -> HELD(hold_id, expires_at) -> SOLD, written with a conditional update; expired holds return to sale; payment confirmation must match the hold id
  • How to choose the hold length, and server-side seat assignment ("best available") to remove conflicts for the hottest seats
  • Data model and partitioning (by event, then section), the read-heavy seat map, and the payment step done idempotently
  • Real numbers: Ticketmaster reported 3.5 billion system requests during the Taylor Swift Eras Tour presale in November 2022

Clarifying questions expected:

  • Do fans choose seats on a map, or can we assign "best available"? General admission too?
  • How long may a fan hold seats while paying? Ticket limits per fan?
  • Is a pre-registration step (verified fans, codes) part of the product?

Code / implementation expected: Optional โ€” the example compares a free-for-all on the best seats with a waiting room plus expiring holds, and shows how hold length changes the sell-out time.

  • booking
  • concurrency

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

8 min read ยท 16 sections

Read the code

Hot on-sale: a free-for-all on the best seats vs a waiting room with server-assigned seat holds that expire
// A hot on-sale: fans fighting over the same seats vs a waiting room that admits fans
// at a fixed rate and lets the server hand out seat holds that expire on a fake clock.
let seed = 42;
const rnd = (n) => { seed = (seed * 48271) % 2147483647; return seed % n; }; // MINSTD, same in every language

console.log("== 1. Load arithmetic (assumptions) ==");
const fans = 1500000, refreshSec = 2;
console.log("fans refreshing every " + refreshSec + " s without a queue: " + fans / refreshSec + " requests/s");
console.log("same fans polling a static queue page every 30 s: " + fans / 30 + " requests/s");
const seats = 70000, perOrder = 4, ordersPerMin = 2000;
console.log("a " + seats + "-seat stadium at " + perOrder + " tickets/order = " + seats / perOrder + " orders, " + seats / perOrder / ordersPerMin + " minutes at " + ordersPerMin + " checkouts/min");

const PAIRS = 500, FANS = 5000;
console.log("\n== 2. Free-for-all: " + FANS + " fans, " + PAIRS + " seat pairs, everyone grabs from the best 10 ==");
let state = new Array(PAIRS).fill("AVAILABLE");
let waiting = FANS, rounds = 0, attempts = 0, wins = 0;
while (waiting > 0 && state.includes("AVAILABLE")) {
  rounds++;
  const best = [];
  for (let i = 0; i < PAIRS && best.length < 10; i++) if (state[i] === "AVAILABLE") best.push(i);
  let won = 0;
  for (let f = 0; f < waiting; f++) {
    const p = best[rnd(best.length)];
    attempts++;
    if (state[p] === "AVAILABLE") { state[p] = "SOLD"; won++; } // UPDATE ... WHERE state = AVAILABLE
  }
  wins += won;
  waiting -= won;
  if (rounds <= 2) console.log("  round " + rounds + ": " + (waiting + won) + " attempts, " + won + " succeed");
}
console.log("  sold out after " + rounds + " rounds: " + attempts + " seat writes for " + wins + " sales, " + ((100 * (attempts - wins)) / attempts).toFixed(1) + "% conflicts");

console.log("\n== 3. Waiting room: admit up to 100 fans/min, server assigns the best free pair ==");
function simulate(HOLD, verbose) {
  seed = 42;
  const st = new Array(PAIRS).fill("AVAILABLE");
  const holds = new Map(); // pair -> {expires, buyAt}; buyAt -1 means the fan walks away
  let nextFan = 0, sold = 0, released = 0, lost = 0, writes = 0, minute = 0;
  while (sold < PAIRS && minute < 120) {
    minute++;
    for (const [p, h] of [...holds.entries()]) {
      if (h.buyAt !== -1 && h.buyAt <= h.expires && h.buyAt <= minute) { st[p] = "SOLD"; sold++; writes++; holds.delete(p); }
      else if (h.expires <= minute) { st[p] = "AVAILABLE"; released++; writes++; if (h.buyAt !== -1) lost++; holds.delete(p); }
    }
    const free = st.filter((x) => x === "AVAILABLE").length;
    const admit = Math.min(100, free, FANS - nextFan);
    for (let i = 0; i < admit; i++) {
      const p = st.indexOf("AVAILABLE"); // best free pair; one allocator per section, so no contention
      st[p] = "HELD";
      writes++;
      nextFan++;
      const buys = rnd(10) < 7; // 70% want to buy and need 1-6 minutes for checkout
      holds.set(p, { expires: minute + HOLD, buyAt: buys ? minute + 1 + rnd(6) : -1 });
    }
    if (verbose && (minute <= 4 || minute % 4 === 0)) console.log("  min " + String(minute).padStart(2) + ": admitted " + String(nextFan).padStart(3) + "  holding " + String(holds.size).padStart(3) + "  sold " + String(sold).padStart(3) + "  released " + String(released).padStart(3));
  }
  return { minute, nextFan, released, lost, writes };
}
const r8 = simulate(8, true);
console.log("  8-min holds: sold out at minute " + r8.minute + ", " + r8.nextFan + " fans admitted, " + (FANS - r8.nextFan) + " told sold out, " + r8.writes + " seat writes, 0 conflicts");
for (const hold of [4, 8, 15]) {
  const r = simulate(hold, false);
  console.log("  hold " + String(hold).padStart(2) + " min: sold out at minute " + String(r.minute).padStart(2) + ", holds released " + String(r.released).padStart(3) + ", buyers cut off mid-checkout " + String(r.lost).padStart(3));
}

console.log("\n== 4. A payment that arrives after its hold expired ==");
const seat = { state: "HELD", holdId: "hold-B" }; // fan A held it first, expired, fan B holds it now
const confirm = (s, holdId) => { if (s.state === "HELD" && s.holdId === holdId) { s.state = "SOLD"; return "SOLD to " + holdId; } return "rejected " + holdId + ", refund the charge"; };
console.log("  late confirm for hold-A: " + confirm(seat, "hold-A"));
console.log("  confirm for hold-B:      " + confirm(seat, "hold-B"));

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 59 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

What is leader election in distributed systems?

Continue