Understanding the problem
Ticketmaster (think BookMyShow for arenas) sells scarce seats under flash-sale load. This interview focuses on view → search → hold → pay without selling the same seat twice — not building the full CMS or pricing engine.
Event + seat map.
<500ms finds.
Hold then pay.
Cache + waiting room.
Pair with Delivery framework, Contention, Scaling reads, Caching, Elasticsearch, and Real-time updates.
Functional requirements
Non-functional requirements
Core entities
Nouns first — full columns later:
- Event — date, description, type, links to venue + performer.
- User — buyer.
- Performer — artist/team/collective.
- Venue — location, capacity, seatMap (JSON / related table for the UI).
- Ticket — seat + price + status (available / held / booked); one per seat when an event is created.
- Booking — user + ticket IDs + payment status (keeps multi-seat orders together).
API
Start simple; evolve booking into reserve + confirm once you hit the checkout UX problem.
GET /events/:eventId -> Event & Venue & Performer & Ticket[]
// tickets drive the interactive seat map
GET /events/search?keyword=&start=&end=&pageSize=&page= -> Event[]
// v1 — evolves later
POST /bookings/:eventId
{ "ticketIds": [...], "paymentDetails": ... } -> bookingId
// v2 after deep dive
POST /bookings/reserve { ticketId, userId } -> bookingId
POST /bookings/confirm { bookingId, paymentToken } -> Booking
HLD — view an event
Client hits the event page → gateway → Event Service → Postgres (event + venue + performer + tickets for the seat map).
- Client
GET /events/:eventId. - API Gateway: auth, rate limit, route.
- Event Service loads event, venue, performer, tickets → response.
HLD — search events
First cut: a Search Service that filters the events table directly. Good enough to draw; too slow for production — fix in deep dives.
HLD — book tickets
Avoid two people paying for the same seat. Use an ACID store — PostgreSQL — with row locks or OCC. Payments via Stripe (tokenize on client; webhook confirms).
Simple POST /bookings that charges immediately works functionally but feels awful: you fill payment and learn the seat vanished. Deep dive #1 fixes that with a timed hold.
Deep dive — reserve then confirm
Hold the seat while the user pays. If they abandon checkout, release it automatically.
- Good: Ticket status =
held+ expires_at; cron / sweeper releases expired holds. - Great: Redis ticket lock
{ticketId → userId}with TTL ~10 min; Booking rowin-progress; Stripe webhook marks ticket sold + booking confirmed (idempotent).
- User picks a seat →
reserve(ticketId, userId). - Booking Service sets Redis lock (TTL 10m) + writes
in-progressbooking → return bookingId. - Client tokenizes card with Stripe.js; server creates PaymentIntent with bookingId in metadata.
- Webhook (idempotent on bookingId): ticket → sold, booking → confirmed; drop lock.
- If TTL expires first, seat is free again.
Deep dive — scale the view path
Hot events hammer one page. Cache event details, venue, and seat-map shells aggressively. Browse can be briefly stale; booking always checks the lock + DB.
Deep dive — virtual waiting room
Millions hitting reserve at once will melt Booking + Redis even with locks. A virtual waiting room admits a controlled concurrency into checkout — better UX than random 5xx, and it protects the write path.
Seat-map freshness is related: polling / short TTL is usually enough; full SSE for every seat click is overkill unless the interviewer pushes real-time. See Real-time updates.
Deep dive — fast search
LIKE '%Taylor%' is a table scan. Move search to Elasticsearch (index name, description, venue, performer, date). Keep Postgres as source of truth; sync via CDC. Enable node query cache for repeated searches.
-- slow starting point
SELECT * FROM Events
WHERE name LIKE '%Taylor%'
OR description LIKE '%Taylor%';
Final design
- CDN — static assets + cacheable event shells.
- Search Service — Elasticsearch (+ query cache).
- Event Service — Redis event cache → Postgres.
- Booking Service — waiting room → Redis TTL locks → Postgres ACID → Stripe webhooks.
Cost and performance levers
What interviewers expect by level
Architecture by level
Three progressive sketches — beginner spine, mid optimizations, pro production depth. Use the matching diagram in the interview; say the tradeoff out loud.
Pros: Clear FR coverage — view, search, book — with ACID booking + Stripe for payments. Cons: Pay-then-discover-seat-gone UX is terrible; SQL LIKE search and uncached event pages die on drop day.
Pros: Redis TTL hold (~10m) fixes checkout UX; event cache absorbs refresh storms; Stripe webhook stays the commit path. Cons: Hold TTL too long starves inventory; too short frustrates payers; confusing event-cache Redis with lock Redis causes ops mistakes.
Pros: Waiting room protects booking under millions of concurrent checkouts; ES+CDC hits <500ms search; CDN helps the read shell. Cons: Waiting rooms are a product decision (fairness vs revenue); CDC lag can show sold-out events as available in search — booking remains truth.
Mid: working spine + no double book. Senior: hold + ES + cache. Staff: drop-day product/tech judgment.
Wrapping up
Ticketmaster is a CAP split: soft freshness for browse/search, hard exclusivity for seats — with timed holds, caches, a waiting room, and a search index making the 10M-user drop survivable.
Related: Delivery framework · Contention · Scaling reads · Caching · Elasticsearch · Real-time updates · PostgreSQL · Design Gopuff · Design Instagram.