The Core Challenge
Ticketing systems are the ultimate interview question because they combine every hard problem in distributed systems:
- High read traffic (browsing shows and seat maps).
- Spiky write traffic (new releases and flash sales).
- Hard correctness (if two users both see "Seat A10 available" and click "Book," only one can win).
- Distributed payments (partial failures everywhere—timeouts, retries, double charges).
Real-World Scale: The Taylor Swift Problem
When Taylor Swift''s Eras Tour went on sale through Ticketmaster, over 14 million users hit the site simultaneously—more traffic than the entire Super Bowl website gets in a year. The system buckled, crashed, and became a national news story. This is the exact problem you''re solving: how do you handle millions of concurrent users competing for a finite number of seats without overselling, crashing, or losing payments?
BookMyShow in India faces similar spikes when blockbuster Bollywood movies or cricket matches go on sale—handling 5-10 million requests per minute during peak releases.
Requirements
Functional
- Browse movies/events by city and date.
- View showtimes and seat maps for a specific show.
- Select seats and temporarily reserve ("hold") them.
- Pay and confirm the booking.
- Cancel/refund based on policies.
Non-Functional
- Correctness over availability for seat inventory—it''s better to show "sold out" than to oversell and cancel tickets later.
- Low-latency browsing—cache browse data aggressively.
- High concurrency for seat selection during flash sales.
- Resilient payment handling across gateway failures and retries.
Capacity Estimation
- Peak browsing: 200K RPS (mostly cacheable queries).
- Peak seat holds during flash sales: 20K RPS.
- Peak confirmed bookings: 5K RPS (payment-gated writes).
API Design
GET /api/v1/cities/{cityId}/shows?date=2026-02-22
GET /api/v1/shows/{showId}/seatmap
POST /api/v1/shows/{showId}/holds
{ "seatIds": ["A10","A11"], "userId": "..." }
→ { "holdId": "...", "expiresAt": "..." }
POST /api/v1/holds/{holdId}/confirm
{ "paymentIntentId": "..." }
→ { "bookingId": "..." }
Data Model
shows show_seats
----- ----------
show_id (PK) show_id + seat_id (PK)
venue_id status (AVAILABLE | HELD | BOOKED)
movie_id hold_id (nullable)
start_time hold_expires_at (nullable)
holds bookings
----- --------
hold_id (PK) booking_id (PK)
show_id show_id
user_id user_id
status (ACTIVE|EXPIRED| seat_ids (array/json)
CONFIRMED) status (CONFIRMED|CANCELLED)
expires_at created_at
Architecture
Clients → CDN (static assets) → API Gateway (auth, rate limits)
├── Show Catalog Service → Cache (Redis) → Read DB
├── Seat Inventory Service → Inventory DB (strong consistency)
├── Booking Service (saga orchestrator) → Event Queue
├── Payment Service (gateway integration)
└── Notification Service (email/SMS confirmation)
The Virtual Waiting Room (Handling Flash Sales)
During high-demand events, don''t let all users hit the seat selection page at once. Instead, implement a virtual queue:
- Users enter a waiting room and receive a queue position.
- Batches of users (e.g., 1,000 at a time) are admitted to the seat selection page.
- Each admitted user has a time-limited session (e.g., 10 minutes) to select and pay.
Ticketmaster''s "Smart Queue" and BookMyShow both use this pattern now. It prevents thundering herd problems and gives admitted users a fair, responsive experience rather than everyone getting a slow, crashing page.
Preventing Double Booking (The Critical Piece)
The key is making seat state changes atomic. Two users clicking "Book Seat A10" at the same millisecond must not both succeed.
Correct Approach: Conditional Update in a Transaction
UPDATE show_seats
SET status = ''HELD'',
hold_id = :holdId,
hold_expires_at = now() + interval ''5 minutes''
WHERE show_id = :showId
AND seat_id IN (:seatIds)
AND status = ''AVAILABLE''
RETURNING seat_id;
If the returned seat count doesn''t match the requested count, rollback everything and tell the user "some seats are no longer available." This atomic check-and-set pattern is the gold standard.
Why Not "Lock in Redis Only"?
Redis locks are useful for performance optimization, but the source of truth must be durable. If Redis restarts and your "locks" disappear, you could oversell seats. Use Redis as a fast-fail check ("is this seat likely available?") but always confirm and commit in the database.
Hold Expiration
Holds must expire automatically (typically 5-10 minutes) to prevent seat hoarding:
- DB-based sweeper: A periodic job finds expired holds and releases seats back to AVAILABLE.
- Event-driven: Schedule a delayed message ("release hold XYZ") when the hold is created.
- Hybrid (recommended): Delayed message for timely release + periodic sweeper as a safety net for missed messages.
Booking Confirmation (Saga Pattern)
- Create hold (seats → HELD).
- Create payment intent with the payment gateway (Stripe, Razorpay).
- On payment success: Mark hold CONFIRMED, seats → BOOKED, create booking record, send confirmation.
- On payment failure/timeout: Let hold expire naturally, seats return to AVAILABLE.
Idempotency is critical: Payment gateways retry webhooks. Your confirm endpoint must handle receiving the same webhook multiple times without creating duplicate bookings or charging twice. Use the paymentIntentId as an idempotency key.
Caching Strategy
- Cache browse data (movies, venues, showtimes) aggressively—this changes infrequently.
- Do NOT heavily cache seat availability. It changes constantly during sales. If you cache it, keep TTL under 2 seconds and always re-verify on hold creation.
- Use Redis for session management and rate limiting during flash sales.
Failure Modes and Graceful Degradation
- Payment callback delayed: Use webhook + polling + timeouts. Don''t release seats until you''re certain payment failed.
- Inventory DB overloaded: Protect with rate limits and fast-fail. Browsing should remain healthy even when booking is degraded.
- Partial outages: Allow users to browse shows and view seat maps even when the booking system is temporarily degraded ("degraded mode").
[!IMPORTANT] Interview tip: The ticket booking system is fundamentally about optimistic concurrency control with the conditional UPDATE pattern. Always explain why the database (not Redis alone) must be the source of truth for seat state, and always mention hold expiration with a sweeper as a safety net.