System Design Road

Medium Interview walkthrough

Ticket booking: the interview, played out

Fifty thousand seats, two million fans, and the sale opens at ten o’clock. If you’ve ever stared at a spinning wheel on a ticket site, you’ve seen this problem from the outside. Here’s how it looks from the inside, and how to design it so no seat is ever sold twice.

10 min read3 interactive diagrams12 sections

This gives away every stage. For a clean attempt, practise it first, then come back.

The short version

  • Browsing can be a few seconds stale; claiming a seat must be exactly right. Design the two paths differently.
  • Claim seats with one conditional update that only works if they’re still free. Never “check, then book”.
  • A hold is valid until its expiry time, checked on every read. Don’t rely on a cleanup job.
  • During a big sale, a waiting room lets fans in at the speed payments can finish.
  • Payments can finish after a hold expires. Use idempotency keys, and refund if the seats are gone.

The 45 minutes

Before we start

Think about booking a seat for a big cricket match or concert. You open the seat map, tap two seats, and a timer starts: “Complete payment in 10:00”. Behind that simple screen are three hard problems.

First, two people can tap the same seat at the same moment, and only one can get it. Second, the timer: if you walk away, those seats must go back on sale on time, even if something crashes. Third, the crowd: for a big show, millions of people arrive in the same minute, far more than there are seats.

Same order as always: requirements, quick maths, data and API, a design that grows, then deep dives.

Minutes 0–5Requirements

Interviewer

Interviewer: Design a ticket booking system like BookMyShow for concerts and cricket matches.

You

You: Do fans pick exact seats, or just a section?

Interviewer

Interviewer: Exact seats, from a seat map.

You

You: How big are the biggest sales?

Interviewer

Interviewer: A big concert: about 2 million fans trying for 50,000 seats when sales open.

You

You: How long can someone hold seats while paying?

Interviewer

Interviewer: 10 minutes. Then the seats go back on sale.

You

You: Is selling one seat to two people ever acceptable?

Interviewer

Interviewer: Never.

You

You: How do payments work?

Interviewer

Interviewer: Through a payment gateway, with cards and UPI. Sometimes it’s slow or doesn’t answer.

You

You: Do we need to be fair and stop bots?

Interviewer

Interviewer: Yes. Real fans should get a fair chance, and one person can buy at most 6 tickets.

Functional requirements

  1. Browse events and see which seats are free.
  2. Pick seats and hold them for 10 minutes.
  3. Pay, and get confirmed tickets.
  4. During big sales, a waiting room lets fans in fairly.

Nice to have: limit tickets per person. Out of scope: refunds, reselling and changing prices based on demand.

Non-functional requirements

  1. Never sell a seat twice. This is the one thing that must always be exactly right.
  2. Handle the rush: 2 million fans in the first few minutes of a sale.
  3. Holds expire on time, even if nothing cleans them up.
  4. Browsing stays up even when booking is at full capacity.
  5. Fair, and hard for bots.

Minutes 5–10Back-of-the-envelope estimates

You

You: 2 million fans arriving over about 10 minutes is 3,300 a second.

You: If they all sat on the seat map refreshing every 5 seconds, that’s 400,000 reads a second. Huge.

You: But here’s the thing: there are only 50,000 seats. At about 2.5 seats per booking, that’s 20,000 bookings in total. And a payment gateway might handle around 300 payments a second, so even perfectly, the whole sale is done in about a minute of payments.

You: So letting 2 million people fight over 50,000 seats at once doesn’t sell anything faster. It just creates load and frustration.

WhatWorkingResult
Fans arriving2M fans over 10 minutes≈ 3,300 a second
Seat map reads, if everyone is let in2M fans refreshing every 5 s≈ 400,000 a second
Fans per seat2M ÷ 50,00040 people for each seat
Bookings possible50,000 seats ÷ 2.5 per booking20,000
Payments~300 a second at the gateway20,000 bookings in about a minute

That’s the insight to say out loud: payments, not servers, decide how fast seats can sell. So we should let people in at about that speed, and keep everyone else calmly waiting.

Minutes 10–15Core entities and the API

Core entities

  • Event: the match or concert, its venue and its time.
  • Seat: one seat for one event, and its state: free, held (by whom, until when) or booked.
  • Hold: a fan’s claim on some seats, with an expiry time.
  • Booking: a paid, confirmed purchase of seats.
  • Payment: the charge at the payment gateway, linked to the hold.

The API

GET/v1/events/881/seats

The seat map. It may be a few seconds old.

Responses
  • 200{ "sections": [ { "name": "North Stand", "free": ["A12", "A13"] } ] }
POST/v1/events/881/holds

Try to hold seats for 10 minutes. You get all of them, or none.

Body
{ "seats": ["A12", "A13"] }
Responses
  • 201{ "hold_id": "h_55", "expires_at": "2026-11-10T10:12:00Z" }
  • 409One of these seats was just taken. Pick again.
POST/v1/holds/h_55/confirm

Pay and turn the hold into a booking. The key stops a retry from charging twice.

Headers
  • Idempotency-Key: pay_h_55
Body
{ "payment_token": "tok_…" }
Responses
  • 201{ "booking_id": "b_9001", "tickets": ["A12", "A13"] }
  • 410The hold expired before payment finished.

Notice that holding is “all or nothing”. If you want A12 and A13 and only A12 is free, you get neither, and can pick again. Nobody wants one of two seats they asked for.

Minutes 15–25The design, built up

Stage 1One server: check the seat, then book it

FansApp serverPostgresseats tablebook A12is it free? then bookTwo fans can both see 'free'and both book seat A12.
72%
You

You: The simplest version: one app server and one Postgres table of seats. To book, check whether the seat is free, and if it is, mark it as booked.

This looks fine until two fans tap the same seat at the same moment. Both checks happen before either booking is written, so both see “free”, and both book. Try it:

Two fans, one seat: A12
match day
Priyahas a ticket for A12Arjunhas a ticket for A12Booking serviceboth read: A12 is freeSeats tableA12: Priya… then Arjun

Both got a confirmation. Two people, one seat, and an argument at the stadium gate.

Booking does

Stage 2Hold seats with one atomic claim, then pay

FansBooking serviceNEWSeats databaseclaim only if still freePayment gatewayNEWHold expiryfrees old holdsNEWhold, confirmchargerelease expired
53%
Green: new in this stage
You

You: The fix is to never split “check” and “book” into two steps. Instead, one statement: set these seats to held for me, until ten minutes from now, but only if they’re still free. The database runs competing statements one after another, so only one can succeed. If fewer rows changed than seats requested, we undo and ask the fan to pick again.

You: Then payment: the booking service charges the fan through the payment gateway, and marks the seats booked, again only if the hold is still theirs.

You: Expiry: each held seat stores its hold-until time. Anywhere we check if a seat is free, a hold whose time has passed counts as free. So holds expire exactly on time, even if nothing cleans them up. A small background job still clears old holds so the seat map looks tidy.

An operation that happens in one indivisible step like this is called atomic. Lesson 1.4 shows how databases make that possible.

Stage 3The flash sale: a waiting room and a cached seat map

FansWaiting roomlets fans in at a set rateNEWCDNevent pages, imagesNEWBooking serviceSeat map cachea few seconds oldNEWSeats databaseone atomic claim per seatPayment gatewayHold expiryfrees old holdsbrowsebuyyour turnseat maphold, confirmchargerefreshrelease expired
52%
Green: new in this stage
You

You: Now 2 million fans arrive at ten o’clock. Two changes.

You: A waiting room in front of booking. Fans who arrive before the sale opens are put in random order, so being half a second faster doesn’t matter. Later arrivals join the back. The room lets people through at about the rate payments can finish, and everyone else sees their place in line and an estimated wait.

You: And the seat map comes from a cache, rebuilt every few seconds, while event pages and images come from a CDN. Browsing never touches the seats database. A map a few seconds old is fine, because the hold step checks the real state anyway.

This is not just a nice idea. Ticketmaster said its 2022 Taylor Swift presale saw about 3.5 billion system requests, four times its previous peak, much of it from bots, and the site struggled badly. A waiting room turns a stampede into a queue.

Stage 4After payment: tickets and emails in the background

FansWaiting roomlets fans in at a set rateCDNevent pages, imagesBooking serviceSeat map cachea few seconds oldSeats databaseone atomic claim per seatPayment gatewayHold expiryfrees old holdsBooking eventsNEWTickets + emailNEWbrowsebuyyour turnseat maphold, confirmchargerefreshrelease expired
44%
Green: new in this stage
You

You: Once a booking is confirmed, the fan shouldn’t wait for tickets to be generated and emails to be sent. The booking is saved, a “booked” event goes onto a queue, and workers create the tickets and send the confirmation email.

(This is the same queue idea as the notification system, and lesson 4.1.)

Checking the design against the requirements

RequirementWhere the design handles it
Browse and see free seatsCDN for pages; seat map from a cache, a few seconds old
Hold seats for 10 minutesOne atomic claim with a hold-until time
Pay and get ticketsPayment gateway with an idempotency key; tickets made in the background
Waiting room for big salesAdmits fans at about the speed payments can finish
Never sell a seat twiceSeats are only claimed with “only if still free” updates
Handle the rushWaiting room in front; browsing served from caches
Holds expire on timeExpired holds count as free on every check
Browsing stays upSeparate cached read path; never touches the seats database
Fair and hard for botsRandom order at the start, per-account limits, bot checks at the entrance

Deep diveNever selling a seat twice

Interviewer

Interviewer: Walk me through the options for claiming a seat.

Bad solutionCheck, then book

Read “is A12 free?”, then write “A12 is mine”. Two requests can both read “free” before either writes. This is the classic race, and during a big sale it happens constantly.

Good solutionLock the row first

SELECT … FOR UPDATE locks the seat’s row, so a second request waits until the first one finishes. Correct. But during a big sale, many requests pile up waiting on the most popular rows, and a slow request holds everyone up.

Great solutionOne conditional update

UPDATE seats SET status = 'held', hold_id = …, hold_until = now() + 10 minutes WHERE event_id = 881 AND seat_id IN ('A12', 'A13') AND (status = 'free' OR hold_until < now()), then check how many rows changed. Two rows means you got both seats. Fewer means someone beat you, so roll back. The database settles it in a single step, there’s no waiting around, and doing it inside one transaction makes multi-seat holds all-or-nothing.

Deep diveHolds that expire on time

Interviewer

Interviewer: How do you make sure abandoned holds go back on sale?

Bad solutionA cleanup job is the only thing that frees holds

If the job runs every minute, seats stay stuck for up to a minute. If the job crashes, they stay stuck until someone notices. During a sale, that’s lost revenue and angry fans.

Good solutionStore an expiry time and check it everywhere

Each held seat has hold_until. Every check for “is this seat free?” treats a hold whose time has passed as free. Expiry is now exact, whatever happens to background jobs.

Great solutionExpiry time, plus tidy-up, plus a little extra time while paying

Keep the expiry check, and let a background job clear old holds every few seconds so the seat map catches up. And when a fan starts paying, extend their hold by a few minutes, so a normal payment never fails just because the timer ran out mid-payment.

Deep diveThe 10 o’clock rush

Interviewer

Interviewer: Two million fans arrive at 10:00. What happens, step by step?

You

You: Without protection, everyone hammers the seat map and the booking service at once. The database slows down, holds time out, payments fail, and fewer tickets get sold, not more.

You: With a waiting room: fans who arrived early are shuffled into random order when the sale opens; later fans join the back. The room lets in a few hundred people a second, about what payments can handle. Everyone else sees “you’re number 48,210, about 12 minutes”.

You: Behind the waiting room, the booking service only ever sees a manageable flow. The seat map is served from a cache that refreshes every few seconds. And when everything’s sold, the waiting room can tell the people still in line right away, instead of letting them in to find nothing left.

Deep divePayments that finish too late

Interviewer

Interviewer: The payment gateway takes 12 minutes to answer. By then the hold has expired and someone else has the seats. What now?

You

You: First, reduce how often it happens: extend the hold when payment starts.

You: Second, when payment does succeed, mark the seats booked with the same kind of conditional update: only if the hold is still mine. If that fails, the seats are gone, so refund the payment automatically and tell the fan.

You: Third, never charge twice. Every confirm request carries an idempotency key built from the hold. If the app times out and retries, the gateway sees the same key and doesn’t charge again. And if we don’t know whether a payment went through, we ask the gateway for its status before doing anything else.

Lesson 7.3 shows that “we don’t know if it worked” moment, which is the hardest part of payments:

The provider is slow, and Chirp’s request times out
charged twice
ChirpPayment providerFan’s bankpay_9f2 · succeeded (2nd try)Chirp’s payment recordFan’s bank statementdebit ₹100 · Chirpdebit ₹100 · Chirp✕ timed out after 10 s

The fan paid ₹200 for a ₹100 tip. A timeout meant “unknown,” not “failed,” and the retry looked like a brand-new payment.

After the timeout

Deep diveBots and scalpers

Interviewer

Interviewer: How do you keep bots from buying everything?

You

You: No single trick works, so I’d layer a few. Only logged-in, verified accounts can enter the waiting room. Bot checks happen at the entrance, not during checkout, so real fans aren’t slowed down at the worst moment. Limit tickets per account and per payment card. Rate-limit by account and device (lesson 6.3). And for the biggest events, give pre-registered fans their own presale with personal codes.

The complete architecture

Here’s everything together: the full design from the last stage, with every part we added along the way. If you only remember one picture from this question, make it this one.

FansWaiting roomlets fans in at a set rateCDNevent pages, imagesBooking serviceSeat map cachea few seconds oldSeats databaseone atomic claim per seatPayment gatewayHold expiryfrees old holdsBooking eventsTickets + emailbrowsebuyyour turnseat maphold, confirmchargerefreshrelease expired
44%

How one request travels through it:

  1. Fans browse the event page and seat map. Pages come from the CDN, and the seat map from a cache refreshed every few seconds.
  2. When a big sale opens, buyers enter a waiting room. It lets people through at about the rate payments can be completed, and shows everyone else their place in line.
  3. When it’s your turn, you pick seats. The booking service claims them in one atomic database update that only succeeds if every seat is still free, and sets a 10-minute expiry.
  4. You pay. The booking service charges you through the payment gateway with an idempotency key, then marks the seats booked, but only if your hold is still valid.
  5. Expired holds simply count as free. A background job also releases them, so the seat map catches up.
  6. A booking event goes on a queue, and workers create tickets and send the confirmation email.

How this answer gets scored

Interviewers don’t tick boxes, but their notes on this question tend to land on the same few moments. Here’s what each one sounds like from the other side of the table.

MomentWhat earns creditWhat costs you
The first five minutesSeparating “see seats” from “claim a seat”Treating all data as needing the same consistency
The numbersNoticing payments limit how fast seats can sellSizing servers for 2 million people at once
Claiming seatsOne conditional update, all-or-nothingCheck, then book
HoldsExpiry time checked on every readA cleanup job as the only way holds end
The rushA waiting room admitting fans at payment speedAutoscaling and hoping
PaymentsIdempotency keys and automatic refunds when seats are goneCharging again on every retry

What the left column has in common: every choice points back to a requirement or a number from earlier. If you can say where each box came from, the interviewer will notice.