System Design

Design Ticketmaster

Step 1 / 9

Learn system design by building an event-ticketing system like Ticketmaster step by step.

The numbers to beat1 seat→ 1 buyer2 writers1 winnernodouble-sell

Deep cut · 13:35

The seat was green when you clicked it. It had been gone for eleven seconds.

The interactive walkthrough above covers the seat map, holds, the atomic flip, payment sagas, sharding and the waiting room. The film is told on the seat map itself — section 112, six rows of seats going red as you watch, one amber seat that is yours for eight minutes — and carries one number the whole way: the share of clicks that land on a seat already gone. It opens at 34 percent and ends at 3.

  • See why the obvious build is not merely slow but wrong: one table, one row per seat, read it to draw the map and write it when somebody clicks. Two people click H14 in the same millisecond, both read free, both write sold, and both get a ticket for one chair. No apology fixes that.
  • See the claim the whole film rests on: two hundred thousand screens against seats changing about twelve times a second is 2.4 million updates every second. A correct map is not slightly hard — it is arithmetically impossible, nobody is trying to fix it, and the design starts by admitting that.
  • See the decision everything else follows from: the map is a suggestion, the click is the question. The answer comes from the real data in eighty milliseconds — yours, or gone — so a wrong click stops being expensive instead of being prevented.
  • Take it into the interview: split read and write paths, a cached map that admits its age, a third state between free and sold, one atomic flip, a saga with an idempotency key across a system you do not own, one shard per show, a sweeper for abandoned holds, and a waiting room that admits three thousand at a time — each bought by a number, with what it costs said out loud.

In the interview room

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

Functional requirements

What it must do — agree on these before drawing a single box.

  • Browse: find an event and venue, render the seat map, and see which seats are available.
  • Reserve: place a short-lived exclusive hold on chosen seats while the buyer checks out.
  • Purchase: claim held seats exactly once and flip them to sold — never double-book.
  • Pay: charge the card via an external provider, then confirm the seats — or release them on failure.
  • Survive an on-sale: admit a stampede of buyers fairly instead of letting everyone storm the door.

Non-functional requirements

The qualities that shape the whole design — each one names the mechanism that buys it.

Never double-sell a seat
The final claim is an ACID transaction against the inventory store: re-check the seat is still held by this buyer and flip held→sold atomically — strongly consistent, exactly-once.
Reserve while a buyer checks out
A short-lived exclusive hold (Redis lock with a 5–10 min TTL) keeps others off the seat, and auto-expires so an abandoned cart frees it.
Money-safe checkout across an external charge
Run it as a saga — hold → charge → confirm on success, release on failure — with an idempotency key so a retried charge never double-bills.
Reads scale cheaply
Browsing is a read-heavy Catalog Service over Seat Inventory; cache and replicate it aggressively so design effort goes to the contended write path.
Survive the on-sale stampede
A virtual waiting room queues buyers and admits them to booking in capacity-sized waves — admission control, not more servers.
Hot events and orphaned holds don’t break things
Shard inventory by event so each show’s hot seats live on their own partition, and run a sweeper that reclaims expired or orphaned holds.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

A strongly-consistent ACID claimover trusting the Redis hold as the record of sale

A cache can lose state when a node dies, and a stale read double-sells the seat. The hold gives speed; the transactional flip to sold is the ironclad exactly-once guarantee — you spend strong consistency exactly here.

A payment sagaover one distributed ACID transaction

You can’t enlist an external card processor and your database in a single ACID transaction. Sequence the steps with a compensating action each (release the hold, refund) and make charges idempotent — atomicity you can’t get, safely approximated.

A short-lived TTL holdover a permanent lock on click

A permanent lock strands the seat forever when a buyer abandons checkout — abandoned carts would sell out the show. A TTL auto-releases, so the seat returns to the pool on its own.

A virtual waiting roomover auto-scaling the booking core

When a million buyers chase a few thousand seats, more servers just let more people fail faster — and you can’t scale a strongly-consistent seat store arbitrarily. Admission control shapes the thundering herd into fair, survivable waves.

What this teaches

Learn system design by building an event-ticketing system like Ticketmaster step by step. An interactive guide covering the read-heavy catalog, the seat-booking race condition, short-lived holds with TTL, strongly-consistent ACID seat claims, the payment saga, a virtual waiting room for on-sale stampedes, sharding and bot defense.

Key takeaways

  • A read-heavy catalog serves events and seat maps over seat inventory.
  • The core challenge is a race condition: one seat, one winner.
  • Short-lived holds (Redis + TTL) reserve seats during checkout.
  • An ACID transaction makes the final seat claim exactly-once.
  • A payment saga charges idempotently, then confirms or releases.
  • A virtual waiting room shapes the on-sale stampede into fair waves.
  • Shard by event, fight bots, and sweep orphaned holds for the real world.

Concepts covered

  • How does Ticketmaster avoid double-booking?
  • Browse events and seats
  • The race condition
  • Reserve before you pay
  • A seat is strongly consistent
  • Charge, then confirm — or undo
  • Tame the on-sale rush
  • Scale, fairness & failure

Design Ticketmaster (Event Booking) — read the full walkthrough as text

the same steps, decisions & trade-offs, for reading, reference & search

The big idea

How does Ticketmaster avoid double-booking?

When tickets for a hot show drop, thousands of people lunge for the same few good seats in the same second. Sell seat 12C to two of them and you’ve created a furious customer and a refund.

Most of this system is easy — browsing events is just a read-heavy catalog. The hard, interesting core is correctness under contention: guaranteeing a seat goes to exactly one buyer, even under a stampede. Everything we build orbits that one guarantee.

How to read this: Each step opens with a real design decision — you make the call before I show you what ships. Watch the diagram grow, and at the end drop payment to see the saga safely undo a half-finished booking. Hit Begin.

Step 1 · The skeleton

Browse events and seats

Before anyone buys, they browse: find a show, pick a venue, see the seat map and what’s open. This part is a classic read-heavy workload.

Design decision: Before buying, people browse events, venues and seat maps. What kind of workload is that, and how do you serve it?

The call: A read-heavy Catalog Service over Seat Inventory; cache aggressively. — Listing events and rendering seat maps scales like any read-heavy site — cache and replicate. Recognizing browsing is the easy 90% lets you concentrate design effort on the hard buying path.

A Catalog Service serves events, venues and seat maps, reading availability from Seat Inventory — the store that tracks each seat’s state (available / held / sold). Browsing can be cached aggressively; it’s the buying that’s hard.

Reads are the easy 90%: Listing events and rendering seat maps scales like any read-heavy site — cache it, replicate it, done. Recognizing that lets you spend your design effort where it actually matters: the write path.

Step 2 · The hard part

The race condition

Two buyers both see seat 12C as available. Both click "buy." Both requests read available, both write sold. Now it’s sold twice. This is a textbook race condition.

Design decision: Two buyers both see 12C as available and both click buy — both read "available," both write "sold." What is this, and the core fix?

The call: A race condition — make "claim this seat" an operation only one request can win. — Read-then-write in two unguarded steps under concurrency is the textbook race. Enforce a single winner — a short-lived hold, then an atomic transaction at purchase. Naming the race is half the answer.

The fix is to make "claim this seat" an operation only one request can win. You cannot let read-then-write happen in two unguarded steps under concurrency. The next steps are two ways to enforce that single winner: a short-lived hold, then a transaction at purchase.

Name the enemy: concurrency: Almost every booking-system question is really testing whether you can prevent two writers from both "winning" the same resource. Spotting the race — and saying so out loud — is half the answer.

Step 3 · Holds & locks

Reserve before you pay

Checkout takes a couple of minutes — entering card details, confirming. You can’t hold a seat open for everyone that whole time, but you also can’t sell it out from under someone mid-payment.

Design decision: Checkout takes minutes. You can’t hold a seat for everyone that long, but can’t sell it from under someone mid-payment. How?

The call: A short-lived exclusive hold (Redis lock with a TTL) during checkout. — The booking service places an exclusive hold with a 5–10 min TTL: while held no one else can take the seat, completing turns it into a sale, and if the buyer vanishes the TTL auto-expires and the seat returns to the pool.

The Booking Service places a short-lived exclusive hold on the chosen seats — a lock in Redis with a TTL (say 5–10 min). While held, no one else can take them. If the buyer completes, the hold becomes a sale; if they vanish, the TTL auto-expires and the seats return to the pool.

Optimistic vs pessimistic: Pessimistic: lock the seat up front (a Redis hold, or SELECT … FOR UPDATE). Optimistic: let the write proceed and reject on a version mismatch. Hot seats favour pessimistic holds; rarely-contended ones favour optimistic.

Step 4 · Make it correct

A seat is strongly consistent

Holds in a cache are fast, but money is involved — you can’t have the seat’s true state drift or get lost if a node dies. The final claim must be exactly once.

Design decision: Holds live in a fast cache, but money is involved and a node can die. How do you make the FINAL seat claim exactly-once?

The call: Confirm in an ACID transaction: re-check held-by-buyer, flip to sold, atomically. — Treat the seat as strongly consistent: inside one transaction, verify it’s still held by this buyer and flip held→sold atomically. The cache hold gives speed; the transactional write gives the ironclad exactly-once guarantee.

Treat the seat as a strongly consistent resource. Confirm the purchase inside an ACID transaction against the inventory store: re-check the seat is still held by this buyer and flip it to sold atomically. The cache hold gives speed; the transactional write gives the ironclad guarantee.

Pick consistency here, on purpose: Most of the site can be eventually consistent. Seat state cannot — a stale read sells a sold seat. Knowing where to spend strong consistency (and pay its latency cost) is the senior move.

Step 5 · Payment saga

Charge, then confirm — or undo

Payment is a slow call to an external provider that can fail, time out, or succeed-but-not-tell-you. You’re holding seats hostage to its answer, and you must never charge twice.

Design decision: Payment is a slow external call that can fail, time out, or succeed-but-not-tell-you — and you’re holding seats. How do you sequence it?

The call: A saga: hold → charge (idempotent) → confirm on success, release on failure. — Sequence the steps and define a compensating action per failure: on success confirm the sale and emit a receipt; on failure/timeout release the hold so seats return. An idempotency key makes a retried charge never double-bill.

Run checkout as a saga: hold seats → charge the Payment Service → on success, confirm the sale and emit a receipt on the event bus; on failure or timeout, release the hold so the seats come back. Use an idempotency key so a retried charge never double-bills.

Distributed transactions = sagas: You can’t wrap an external card charge and your database in one ACID transaction. Instead, sequence the steps and define a compensating action (release the seats, refund) for each failure — that’s a saga.

Step 6 · The stampede

Tame the on-sale rush

A blockbuster on-sale brings a million people in one minute. Even a perfect booking core melts if all of them hit it simultaneously — and the experience becomes a lottery of errors.

Design decision: A blockbuster on-sale brings a million people in one minute. Even a perfect booking core melts if all hit at once. What do you do?

The call: A virtual waiting room: queue buyers and admit them in controlled waves. — At peak the gateway diverts buyers into a fair queue and admits them to booking in waves sized to capacity. It converts a thundering herd into a steady trickle and makes fairness explicit — admission control, not more servers.

Front the system with a virtual waiting room. At peak, the gateway diverts buyers into a fair queue and admits them to the booking flow in controlled waves, sized to what inventory and the booking service can handle. The crowd waits in an orderly line instead of crashing the door.

Shed and shape load: When demand vastly exceeds capacity, the answer isn’t more servers — it’s admission control. A waiting room converts a thundering herd into a steady, survivable trickle, and makes fairness explicit.

Step 7 · The sharp edges

Scale, fairness & failure

Big venues and many simultaneous events strain one inventory store; bots try to scalp; and holds can leak if a booking node dies mid-flow.

Shard inventory by event so each show’s hot seats live on their own partition. Add bot defenses (CAPTCHAs, per-account limits) and queue fairness to fight scalpers. Run a sweeper that reclaims expired or orphaned holds, so a crashed checkout never permanently strands a seat.

Design for the unhappy path: Crashed checkout → sweeper reclaims the hold. Scalper bot → rate limits and CAPTCHAs. Hot event → its own inventory shard. Handling the failures and abuse is what separates a real ticketing system from a demo.

You did it

You just designed Ticketmaster.

  • A read-heavy catalog serves events and seat maps over seat inventory.
  • The core challenge is a race condition: one seat, one winner.
  • Short-lived holds (Redis + TTL) reserve seats during checkout.
  • An ACID transaction makes the final seat claim exactly-once.
  • A payment saga charges idempotently, then confirms or releases.
  • A virtual waiting room shapes the on-sale stampede into fair waves.
  • Shard by event, fight bots, and sweep orphaned holds for the real world.
RUN IT YOURSELF

No double-booking: optimistic locking

Selling the last seat to exactly one person is a concurrency problem. Optimistic locking (compare-and-set on a version) solves it. Here it is in both languages, running live. Switch tabs, read the comments, and hit Run.

HOW TO READ THE CODE — 4 IDEAS
  1. Every seat carries a version number alongside its owner.
  2. To book, you pass the version you read earlier; the store commits only if it still matches (step 1).
  3. If someone booked first, the version moved on, so your compare-and-set fails (step 2) — you would retry.
  4. The winner claims the seat and bumps the version (step 3); no two people get the same seat.
CPython · WebAssembly
built to be booked, not memorized — make the calls, drop payment, run the gauntlet.
Finished this one? 0 / 65 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs