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): reserve first, then confirm with payment; an unconfirmed hold 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 and best-available - and best-available still takes atomic holds on concrete seats, never a vague count.

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

Yes while the hold lives: added seats take their own holds joined to the same booking atomically - all confirm together or the addition fails cleanly.

06Sold out - do we need a waitlist?

Out of scope day one, but expired-hold releases are emitted as observable events so a future waitlist can subscribe without redesign.

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?

Consistency for booking — a seat never sells twice; availability for browse/search — the seat map may lag seconds behind reality.

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

One hot event can draw ~10M users at on-sale — admission control (waiting room + token bucket) absorbs the spike before it ever reaches seat inventory.

03How fast should browsing and search feel?

Search under ~500 ms; seat-map views served cache/CDN-first. 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, and holds must outlive payment-provider latency.

05What happens when a confirm request or payment webhook arrives twice?

Confirm is idempotent per booking_id: retries and duplicate webhooks never double-charge or double-book.

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?
  • How long should a hold last, and can it extend once payment starts?
  • Is browse allowed to show slightly stale availability?
  • One global on-sale moment or staggered? What peak concurrent users should I plan for?
  • 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 contending for ~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 + 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 a seat must happen one at a time, while 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 — no specific seats, just a capacity number — per-seat locking is overkill. I would keep one atomic counter of remaining capacity and simply stop selling when it hits zero.

03

Architecture path

Draw the write path and the read path as separate lanes — they carry different traffic and justify different components.

Ticketmaster serviceClientBooking path — reserve, then confirmAdmission ControlBooking ServiceHold Store (TTL)Seat Inventory DBPayment ServiceBrowse path — search and seat map (read-heavy)CDN / EdgeEvent + SearchServiceSeat-Map CacheDatabaseservicestorequeue / logexternalasync

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.

Reserve takes a TTL hold; confirm charges payment idempotently and flips seats to booked in one atomic transition — check and update must be serialized (row lock or version CAS), because a check-then-act gap is a TOCTOU (time-of-check-to-time-of-use) race that double-sells.

Event pages are pre-rendered and CDN-cached so a flash sale never hits app servers for static content; availability may lag 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.

01

FocusSeat states

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

AvoidHolds without a TTL — abandoned carts lock seats forever.

02

FocusReserve first, confirm later

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

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

03

FocusStopping double-sell

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

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

04

FocusSurviving the on-sale spike

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

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

05

FocusPaying exactly once

AskThe 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?

AvoidNo 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 →