Free full guide

Ticketmaster

Design Ticketmaster — an event ticketing system that sells reserved seats under extreme flash-sale contention without ever double-selling a seat.

00

Practice checkpoints

The interview rhythm stays compact, so the page can spend attention on the actual design decisions.

  1. 01
    Clarify scope
  2. 02
    Requirements + scale
  3. 03
    API + data model
  4. 04
    Draw architecture
  5. 05
    Deep dive
  6. 06
    Trade-off decision
01

Requirements that shape the design

Do not only state requirements. Ask for them. Each card pairs the design constraint with a clarification question you can say out loud before drawing the architecture.

Functional requirements

01What must users do — browse and search events, or is this booking-only?

Users can view an event with its venue seat map and near-real-time availability, and search events by keyword, date, and location.

02When a user picks seats, do they get a temporary hold while they pay?

Users can select seats and get a short-lived hold (5-10 minutes). They reserve first, then confirm with payment. A hold that is never confirmed auto-releases.

03What is the one guarantee we can never break?

A booking is final: each seat sells exactly once — no double-booking, ever, even during a flash on-sale.

04Seat-map selection, best-available allocation, or both?

Both. Map selection for reserved venues, plus best-available. Even best-available takes atomic holds on specific seats, never a vague count.

05Can a buyer add seats to an existing hold mid-checkout?

Yes, while the hold is still alive. Added seats take their own holds, joined atomically to the same booking. They all confirm together, or the addition fails cleanly.

06Sold out - do we need a waitlist?

Out of scope day one — but a waitlist is a likely follow-up, so day-one choices shouldn't make adding one painful.

Out of scopeDynamic pricing for hot events · Admin and event-coordinator tooling for creating events · Viewing past bookings, ticket transfer, and resale

Non-functional requirements

01Where do we need strong consistency, and where can data be stale?

Booking needs strong consistency, so a seat never sells twice. Browse and search only need availability. The seat map can lag a few seconds behind reality.

02What does a flash on-sale look like at peak?

One hot event can draw ~10M users at the on-sale moment. The system has to absorb that spike. It's fine to make users wait fairly rather than fail them.

03How fast should browsing and search feel?

Search under ~500 ms; seat-map views must feel instant even during a rush. Read-heavy overall at roughly 100:1.

04How long can a hold last, and what happens on expiry?

Holds are short (5-10 minutes) with TTL auto-release. Abandoned checkouts return seats without manual cleanup. Holds must also outlive payment-provider latency.

05If a confirm arrives twice — a double click, or the payment provider retrying a webhook — must the charge and the sale still happen only once?

Retries and duplicate payment webhooks must never double-charge or double-book — confirming twice must have the same effect as confirming once.

Keep asking — the interview is a conversation

Real interviews probe far more than a tidy list. These are the scope questions that separate candidates who interrogate the problem from those who recite it.

  • Reserved seating with a seat map, or general admission with a capacity counter — or both?
  • Do refunds or cancellations put seats back on sale, or is that out of scope?
  • Is there a per-user ticket limit per event that we have to enforce?
  • Do we handle card details ourselves, or hand off to a payment provider — does PCI compliance land on us?
  • Is bot and scalper mitigation in scope?
02

Numbers that force architecture decisions

Treat every estimate as a pressure that justifies a component: cache, queue, partition, replica, worker pool, or fallback path.

01

On-sale contention

~10M users (from the on-sale requirement) contending for an assumed ~50K seats at one on-sale moment10,000,000 ÷ 50,000 ≈ 200 users per seat

Almost everyone must be absorbed by the waiting room — admission control decides who even reaches booking.

02

Reserve write burst

First minute of on-sale: admitted users each attempt a holdadmit 50K users/min ≈ 800 reserve attempts/s against one event partition

Per-event partitioning and short atomic transitions keep the hot partition alive. Other events stay unaffected.

03

Browse read skew

Read-heavy at ~100:1 — seat-map polling dominates800 writes/s × 100 ≈ 80K seat-map reads/s at peak

Serve views from cache/CDN with seconds of staleness — the inventory database never sees browse traffic.

04

Hold TTL vs payment latency

External payment confirm p95 is seconds; users fill forms for minutesTTL 5-10 min ≫ payment p95 ~3-10 s

Generous TTL avoids holds expiring mid-payment; expiry auto-releases abandoned seats without cleanup jobs.

05

Search latency budget

Search target < 500 ms end-to-endinverted-index query ~50-100 ms + ranking + network ≈ well under 500 ms

A full-text index (not LIKE scans) is required; cache repeated queries and CDN non-personalized results.

Decision example

The numbers

Picture the on-sale: ~10M people want ~50K seats — about 200 people per seat. The fight is over each seat's state, so writes to one seat must happen one at a time. Meanwhile, everyone who is just browsing gets served from cache.

My choice

I would do three things. First, put everyone in a waiting room and let them into the booking flow at a controlled rate, so the seat database only ever sees traffic it can survive. Second, when a user picks seats, hold them for 5-10 minutes — pay inside that window or the seats go back on sale automatically. Third, make "check the seat is free AND mark it held" one single atomic step (a row lock, or an update that only succeeds if nobody changed the seat in between), so two buyers can never both grab it.

Avoid

What I would NOT do: read "seat is free" first, then mark it held as a second step. In the gap between those two steps another buyer can do the same read — both think they won, and the seat sells twice. The check and the update must be one step, and the loser should get a clear "someone beat you to it" response (HTTP 409), not a silent failure.

Change if

If the venue is general admission, with no specific seats and just a capacity number, per-seat locking is overkill. I would keep one atomic counter of remaining capacity and stop selling when it hits zero.

03

Architecture path

One complete picture first, then each path as its own diagram — the write path and the read path carry different traffic and justify different components.

Complete picture

Overview — every component

ClientAdmissionControlBooking ServiceHold Store (TTL)Seat InventoryDBPayment Serviceidempotent confirmSeat-Map Cache +Searchbrowse / search reads

Booking is the spine; browse and search never touch seat inventory (dashed = read side, off the booking hot path). An expired hold returns seats automatically.

Path 1

Booking path — reserve, then confirm

ClientAdmissionControlBooking ServiceHold Store (TTL)Seat InventoryDBPayment Service
  • Reserve places a hold with a countdown (TTL) — if the buyer never pays, the hold expires and the seat goes back on sale by itself.
  • Confirm charges payment and flips the held seats to sold in one atomic step; retrying the confirm never charges or books twice.
  • "Is this seat free?" and "mark it held" must be one single step — a row lock, or an update that only succeeds if the seat hasn't changed underneath it (compare-and-set).
  • If those were two separate steps, two buyers could both pass the check in the gap between them — and the seat would sell twice.

Path 2

Browse path — search and seat map (read-heavy)

ClientCDN / EdgeEvent + SearchServiceSeat-Map CacheDatabase

Event pages are pre-rendered and CDN-cached, so a flash sale never hits app servers for static content. Availability may lag a few seconds. The booking transaction is where truth is enforced.

04

API and data model

Before optimizing, make the contract inspectable: endpoints, entities, ownership, retries, and state.

GET/events/{event_id}

res200 { event, venue, seat_map, availability }

Cache/CDN-first; availability may lag seconds — booking is where truth is enforced.

GET/events/search?keyword&date&location

res200 Event[]

Inverted index (full-text) — sub-500 ms including fuzzy matches.

POST/bookings

req{ event_id, ticket_ids[] }

res201 { booking_id, hold_expires_at } · 409 seat already held or sold

Creates the TTL hold: seats flip to held atomically or the whole request fails — no partial holds.

POST/bookings/{booking_id}/confirm

req{ payment_token, idempotency_key }

res200 confirmed · 402 payment failed (hold keeps ticking) · 410 hold expired

Idempotent per booking_id: payment-provider retries and duplicate webhooks are safe.

Core entities

Event

event_id (PK) · venue_id · performer · starts_at · on_sale_at

Ticket

ticket_id (PK) · event_id · section/row/seat · price · status: available/held/booked · version

status + version drive optimistic concurrency; partition by event_id so one on-sale cannot degrade other events.

Booking

booking_id (PK) · user_id · ticket_ids · total · status: pending/confirmed/failed · idempotency_key

User

user_id (PK) · email · created_at

05

Deep dive directions

Pick one lane for the final third of the interview. Each lane gives you the topic, the interviewer question it should answer, and the failure mode to avoid.

Focus

Seat states

Ask

Walk a seat through available → held → sold. What exactly flips each state, and can two people ever hold the same seat?

Answer

One atomic write flips a seat from available to held. It succeeds only if the seat is still available, so a second buyer just fails. Two people can't hold the same seat, because the check and the flip are one step. Confirm turns held into sold after payment; an expired TTL sends it back to available.

Avoid

Holds without a TTL — abandoned carts lock seats forever.

Focus

Reserve first, confirm later

Ask

Why hold the seat before payment, and what happens to it if the payment never finishes?

Answer

Hold the seat first so it stays the buyer's while they type card details. If you marked it sold at reserve time, every failed or abandoned payment would strand a seat that can't be sold. When payment never finishes, the TTL expires the hold and the seat goes back on sale automatically — no cleanup job needed.

Avoid

Marking the seat sold at reserve time — a failed payment then makes it unsellable.

Focus

Stopping double-sell

Ask

Row lock, version check (CAS), or atomic counter — how does each stop two buyers winning the same seat, and what does each cost under contention?

Answer

For reserved seating, use short row locks or single-statement conditional updates, and cap contention with admission control. A row lock serializes buyers: always correct, but they queue on hot rows. A version check (CAS) skips locks, but at ~200 buyers per seat most retry and keep losing. An atomic counter fits only interchangeable seats — general admission.

Avoid

Pure optimistic retries in a flash sale — most buyers fail again and again instead of waiting fairly.

Focus

Surviving the on-sale spike

Ask

10M people hit one on-sale. How do you let them wait fairly instead of erroring most of them out?

Answer

Put all 10M into a waiting room and admit them at a controlled rate. A token bucket releases roughly 50K users a minute into the booking flow, so seat inventory only ever sees traffic it can survive. Everyone else keeps a fair queue position instead of hammering retry against errors. Other events never feel the spike.

Avoid

Letting the whole herd reach seat inventory — the waiting room exists to protect it.

Focus

Paying exactly once

Ask

The confirm request arrives twice — a retry or a double click. How do you make sure the card is charged once and the seat sold once?

Answer

Confirm carries an idempotency key, the booking_id, and the service stores the outcome of the first attempt. Any retry or duplicate webhook with the same key gets that stored result back instead of running again. So however many times the request arrives, the card is charged once and the seat sold once.

Avoid

No idempotency key on confirm — a network retry double-charges or double-sells.

Ready to practice?

Talk through Ticketmaster out loud and get AI scoring on the explanation.

Practice this with AI →