Javathoughts Logo
Javathoughts
Published on
Views

Design a Ticket Booking System Design Interview Guide

Authors
  • avatar
    Name
    Javed Shaikh
    Twitter

← System Design Interview Preparation

This guide walks through Design a Ticket Booking System the way you would in a backend or Java interview. The numbers are interview estimates. They help you show your thinking. They are not a production capacity plan.


1. Problem

Concerts, movies, and trains sell unique seats. Two people must not get seat 12A.

Example:

  • Browse events and a seat map
  • Click two seats → they lock for 8 minutes
  • Pay
  • Tickets are issued
  • If they walk away, locks expire and seats return
  • On sale morning, traffic is a spike, not an average

This is e-commerce inventory with hard uniqueness and a timer.


2. Functional Requirements / FR

RequirementWhat it means
Event listingShows, venues, times.
Seat mapAvailable / locked / sold.
Seat lockingHold for this user until TTL.
Booking flowLock → pay → confirm.
PaymentSame rules as payment system.
Lock expiryWorker or TTL frees seats.
No double bookingUnique constraint on seat + show.
Waiting roomOptional queue before the map on huge onsales.
IdempotencyRetry lock/pay safely.

Out of scope: dynamic pricing engine internals, paper tickets, full fraud product.


3. Non-Functional Requirements / NFR

RequirementWhy it matters
Fairness under spikeSale opens at 10:00.
Lock correctnessRedis TTL and DB unique both help.
Low latency mapSeat map should feel live.
Idempotent payDouble submit cannot sell twice.
High availability of readsBrowsing can degrade; selling cannot lie.

Interview line: lock with TTL, confirm with a unique booking row, pay in between.


4. Back-of-the-Envelope Calculation

Traffic assumptions

  • Popular onsale: 1 million users in 10 minutes trying to buy 20,000 seats
  • Steady state: 5 million map views/day across many events
  • Read/write: map reads dominate; locks/writes cluster at onsale

QPS

Steady map views:

5,000,000 / 86,400 ≈ 58 QPS average
If peak is 5x, around 290 QPS — easy.

Onsale spike (1 million users / 10 minutes):

1,000,000 / 600 seconds ≈ 1,670 QPS of “enter sale”
Seat clicks could be 5× that → around 8,000 QPS
If you call that 5x a “peak on peak”, design lock service for around 8,000–10,000 QPS.

Successful bookings cannot exceed seats: 20,000 writes, not millions.

Storage

Seat row tiny. 20k seats × 200 bytes = a few MB per show. Bookings 20k × 1 KB = 20 MB. History is the long-term store.

Cache / memory

Seat map in Redis: 20k seats × 32 bytes ≈ 640 KB per show. Hundreds of live shows still fit in memory.

Locks: Redis keys showId:seatId with TTL.

Servers

Waiting room + gateway absorb the 8k QPS. Booking/lock nodes: 10–20 if each handles ~500 lock ops/sec. Interview estimates, not exact production numbers.


5. APIs

GET  /v1/events
GET  /v1/events/{eventId}/seatmap

POST /v1/holds
Idempotency-Key: <uuid>
{ "eventId", "seatIds": ["12A","12B"] }

POST /v1/bookings
Idempotency-Key: <uuid>
{ "holdId", "paymentMethodToken" }

POST /v1/webhooks/payment

Waiting room (optional): GET /v1/sales/{eventId}/ticket returns a wait token.

Errors: 409 seat taken, 410 hold expired, 429 waiting room.


6. Data Model

events / seats

Seat is unique per event_id + seat_id. Status: open, held, sold.

holds

hold_id, user_id, event_id, seat_ids, expires_at, status.

bookings

booking_id, hold_id, payment_id, status, unique (event_id, seat_id) on sold seats table.

Redis lock

SET hold:{event}:{seat} {userId} NX EX 480 (8 minutes).

DB unique index is the last line of defense if Redis is wrong.


7. High-Level Design

Locks are the hot path. Bookings are durable.

Ticket Booking architecture

Ticket Booking architectureSeat locks live in Redis with a short TTL. Only a paid lock becomes a booking row. An expiry worker frees seats that never paid.confirmreleaseticket👤User🌐API Gateway⚙️Booking Service🎟️Seat Lock Service🧠Redis / Lock Store🗄️Booking DB💳Payment Service👷Expiry Worker🔔Notification
Seat locks live in Redis with a short TTL. Only a paid lock becomes a booking row. An expiry worker frees seats that never paid.

Components:

  • User → API Gateway → Booking Service
  • Seat Lock Service → Redis
  • Booking DB on confirm
  • Payment Service
  • Expiry worker releases leftover holds
  • Notification after ticket

Flow:

  1. (Optional) waiting room token.
  2. Load seat map from cache.
  3. SET NX each seat. All-or-nothing: if one fails, release the others.
  4. Pay with hold id.
  5. Insert booking; mark seats sold; delete lock keys or let them expire harmlessly.
  6. Email tickets.

Redis TTL is the clock. The worker is backup for DB held rows.


8. Deep Dives

Prevent double booking

  1. Redis NX
  2. Unique (event_id, seat_id) where status=sold
  3. Hold owned by user_id — another user cannot confirm your seats

Lock expiry

8–10 minutes is a common interview number. Payment start can extend TTL once.

Waiting room

Issue sequential tokens. Gateway only forwards N users/sec to the map. Extra users poll. This is rate limiting plus a queue, not a fake progress bar.

Idempotency

Same hold request returns the same holdId if seats still yours. Checkout key maps to one booking_id.

High spikes

Cache the map. Do not hit SQL per hover. Pub/sub optional to refresh sold seats; polling every 2s is acceptable in interview.


9. Bottlenecks

  • Redis hot keys on one event (shard by eventId — one event still hot)
  • Seat map thundering herd at T=0
  • Payment provider slower than lock TTL
  • Unfair bots — CAPTCHA / waiting room / account limits

10. Tradeoffs

ChoiceUpsideDownside
Redis locksFastNeed DB unique too
SQL SELECT FOR UPDATESimpleOnsale will choke
Long TTLUsers finish paySeats frozen
Waiting roomProtects coreFeels slow

Pick: Redis NX + TTL, SQL unique on sold, waiting room for famous onsales.


11. Failure Modes

FailureHandling
Redis downFail holds (do not sell from stale map)
Paid after TTLTry re-lock; else refund
Worker downRedis TTL still frees; DB held rows catch up later
Duplicate confirmUnique seat constraint
Bot armyWaiting room + rate limit

12. Interview Answer in 10 Minutes

"Ticket booking is unique inventory with a timer.

Average traffic is small. The onsale is the design: a million people in 10 minutes is about 1,670 QPS to enter, maybe 8,000 seat clicks. Only 20,000 seats can sell. I would put a waiting room in front, serve the seat map from cache, and lock seats in Redis with SET NX and an 8-minute TTL.

Checkout uses an idempotency key, charges payment, then writes a booking row with a unique constraint on event plus seat. An expiry worker is backup. If payment is late, we refund rather than double-sell.

The map is a hint. The lock plus the unique row is the truth."


13. Interview Talking Points

  • SET NX + TTL.
  • Unique sold seat.
  • All-or-nothing multi-seat holds.
  • Waiting room for spikes.
  • Extend lock when payment starts.
  • Metrics: lock failure rate, expired holds, double-sell (must be 0), time-to-ticket.
  • Same money path as payments.

14. Follow-up Questions

GA (no seats)?
Decrement a counter with the same conditional pattern as e-commerce stock.

Trains with waitlist?
After sold out, queue requests; promote when a booking cancels.

Multi-city inventory?
Partition locks by event_id.

WebSockets for map?
Nice to have. Polling is enough if you say why.


Related JavaThoughts reading:


Next in this series: Design Logging, Metrics, and Observability Platform.