CASE STUDY

BookMyShow

9 min read·1,766 words·Beginner

How to use this case study

SDE-2 / Mid

Study the full BookMyShow case study. Focus on understanding the core components and how they interact. Focus on sections 1-3: requirements, API design, and high-level architecture. Understand the seat locking mechanism and the booking flow.

SDE-3 / Senior

Study the full BookMyShow case study. Focus on understanding the core components and how they interact. Be ready to discuss the atomic seat locking with Redis, how to handle peak traffic during blockbuster releases, and the payment integration with idempotency.

Staff / Principal

Study the full BookMyShow case study. Focus on understanding the core components and how they interact. Be prepared to discuss the distributed seat inventory system, how to handle 10K+ concurrent booking requests, and the dynamic pricing engine. Discuss the show scheduling and theater management architecture.


0) Problem Restatement

Design a system like BookMyShow where users can browse movies, select seats in a theater, and complete booking with payments. The core challenge is preventing overselling of seats while keeping the system scalable.


1) Requirements

1.1 Functional

  • Browse movies, shows, theaters.
  • See seat layout with availability.
  • Select and hold seats.
  • Complete booking with payment.
  • Cancel/refund tickets.

1.2 Non-Functional

  • Consistency on seat booking (no double-booking).
  • Scalability (peak traffic during blockbuster releases).
  • Low latency for seat availability (< 1s refresh).
  • High availability, fault tolerance.


2) High-Level Architecture

2.1 Overview

  • Client (App/Web) → API Gateway → Services (Show, Inventory, Payment) → Cache/DB → Notifications.
  • Key challenges: seat locking (atomic, low-latency), payment integration (async), and scaling under spikes.

2.2 Flow Diagram

Architecture Diagram

flowchart LR

    %% User Interaction
    U["User - Web/Mobile"] -->|"GET /shows?location=X&date=Y"| AG["API Gateway"]
    U -->|"POST /selectSeats {userId, showId, seats}"| AG

    %% Show Metadata on top
    AG -->|"GET /shows?location=X&date=Y"| SS["Show Service"]
    SS -->|"SELECT show & seat info from DB"| DB[(Booking DB)]

    %% Inventory & Payment at bottom
    AG -->|"POST /holdSeats {showId, seats, userId}"| IS["Inventory Service"]
    IS -->|"atomicCheckHold({showId, seats})"| Redis[(Redis Cache)]
    IS -->|"INSERT booking record in DB"| DB
    IS -->|"POST /initiatePayment {bookingId, userId, amount}"| PS["Payment Service"]
    PS -->|"POST /paymentCallback {bookingId, status}"| IS
    IS -->|"UPDATE booking status in DB"| DB

    %% Async Events
    IS -->|"emitBookingEvent({bookingId, userId, status})"| MQ[(Message Queue / Kafka)]
    MQ -->|"consume for notifications"| NS["Notification Service"]
    MQ -->|"consume for analytics"| Analytics[(Analytics / BI)]
    PS -->|"optionalPaymentEvents({paymentId, status})"| MQ

    %% Styling
    classDef userFlow fill:#f0f8ff,stroke:#333,stroke-width:1px;
    classDef coreService fill:#e0ffe0,stroke:#333,stroke-width:1px;
    classDef asyncFlow fill:#fff0f0,stroke:#333,stroke-width:1px;
    classDef showMeta fill:#fef7e0,stroke:#333,stroke-width:1px;

    class U,AG userFlow;
    class IS,DB,PS,Redis coreService;
    class MQ,NS,Analytics asyncFlow;
    class SS showMeta;

    %% Offsets to avoid overlapping labels
    linkStyle 5 offset:-20px;
    linkStyle 6 offset:20px;

3) Components (what & why)

Client (App/Web)

  • UI for browsing shows, seat map, selecting seats, and paying.
  • Requests seat hold and completes payment.
  • Shows confirmation and stores tickets.

API Gateway

  • Authentication, authorization, rate limiting, request validation.
  • Routes to Show Service, Inventory Service, and Payment Service.
  • Provides edge-level protections and metrics.

Show Service

  • Movie metadata: movies, theaters, screens, show times.
  • Serves seat layout templates and static show info.

Seat Inventory Service (Core)

  • Maintains seat states: AVAILABLE, HELD, BOOKED.
  • Core responsibilities:
  • Atomically allocate/hold seats.
  • Maintain holds with TTL.
  • Confirm seats after successful payment.
  • Release seats on timeout/payment failure.
  • Should be horizontally scalable and partitioned by show_id.

Cache / DB

  • Redis (hot) for seat availability lookups and fast holds (with TTL).
  • Primary DB (SQL/NoSQL) for persistent bookings and audit trail.
  • Use CQRS: reads from cache, writes through Inventory Service to DB.

Locking Mechanism

  • Use atomic Redis operations (SETNX, Lua scripts) or DB row-level transactions for final persist.
  • TTL-based holds to auto-release uncompleted holds.

Payment Service

  • Integrates with external gateways (Stripe/PayU/Paytm).
  • Handles async callbacks, retries, idempotency tokens, and refunds.
  • Marks booking CONFIRMED on success.

Notification Service

  • Sends email/SMS/push confirmations and tickets.
  • Asynchronous worker processing.


4) Data Model (minimal)

Movie

Movie(movie_id, title, language, duration, genre)

Show

Show(show_id, movie_id, theater_id, screen_id, start_time, end_time)

Seat (per show)

Seat(seat_id, show_id, row, col, status) -- status ∈ {AVAILABLE, HELD, BOOKED}

Booking

Booking(booking_id, user_id, show_id, seats[], status, created_at, expiry_time)
-- status ∈ {PENDING, CONFIRMED, CANCELLED}

5) Key Flows

5.1 Browse Flow

  1. Client requests shows and seat layout.
  2. Show Service returns metadata and seat template.
  3. Seat availability comes from Redis (fast read).

5.2 Seat Hold (Select Seats)

  1. User selects seats → client calls Inventory Service: /holdSeats(show_id, seats[], user_id).
  2. Inventory Service atomically sets all requested seats to HELD in Redis with an expires_at (e.g., now + 5 minutes) using a Lua script — all seats or none.
  3. Service returns a hold_id (temp reservation) to the client.

5.3 Payment & Confirm

  1. Client initiates payment with Payment Service using idempotency token.
  2. Payment Service calls gateway; on success callback:
  • Payment Service notifies Inventory Service to confirm hold_id.
  • Inventory Service persists booking to DB, marks seats BOOKED, and removes TTL holds in Redis.
3. On failure or timeout:

  • An expired hold stops counting: the hold script treats a HELD seat whose expires_at has passed as AVAILABLE, and a cleanup job resets stale entries.
  • Payment failure: release the hold immediately so the seats go back on sale.
4. Payment succeeds after the hold expired (the key edge case): the seats may already belong to someone else. See Deep Dive B, "Late Payment".

5.4 Cancellation / Refund

  • Cancel booking → mark CANCELLED in DB, release seats (update Redis), initiate refund if applicable.


6) Concurrency & Correctness (Deep Dive A: Seat Locking, ~8–10 mins)

Problem

Multiple users may attempt to hold the same seat concurrently. We must prevent double-booking while keeping latency low.

Approach

  1. Redis Atomic Hold (preferred for latency)
  • Maintain a Redis hash or bitmap per show_id (e.g., show:{show_id}:seats).
  • Use a Lua script that checks all requested seats are AVAILABLE and atomically sets them to HELD with metadata {user_id, hold_id, expires_at}.
  • Script returns success/failure, thereby guaranteeing atomic multi-seat holds.

  1. Expiry for Holds
  • Store expires_at in each held seat's entry. Seats live as fields in one hash per show, and hash fields don't have their own TTLs on most Redis versions, so expiry is checked in the script: a HELD seat past its expires_at counts as AVAILABLE.
  • A cleanup job resets expired holds for tidy seat maps; correctness doesn't depend on it running on time.
  • If the user does not complete payment, the seats become available again as soon as the hold expires.

  1. DB Confirmation (durable)
  • After payment success, perform a DB transaction to persist booking and set seats to BOOKED.
  • Use an idempotency token to ensure re-entrant safety for retries.

  1. Fallback to DB Locking (if Redis fails)
  • Use DB row-level locking or optimistic concurrency (version numbers) as a fallback to ensure correctness.

Edge Cases & Mitigations

  • Partial success in multi-seat hold: Lua script ensures all-or-nothing holds.
  • Redis crash before DB persist: Use a short window; on DB reconcile job, mark seats from HELD to AVAILABLE if not confirmed.
  • Clock skew / TTL drift: Use server-side timestamps and conservatively short TTLs.


7) Deep Dive B: Payments & Idempotency (~8–10 mins)

Payment Flow Characteristics

  • External gateways are async and may be slow or flaky.
  • Must avoid double-charging and ensure eventual consistency.

Steps

  1. Initiate Payment
  • Client posts to Payment Service with hold_id and an idempotency token.
  • Payment Service calls gateway.

  1. On Gateway Callback
  • Gateway calls our callback URL with transaction status.
  • Payment Service verifies callback signature, matches idempotency token, and notifies Inventory Service.

  1. Confirm Booking
  • Inventory Service checks, in one atomic script, that every seat is still HELD by *this* hold_id — even if expires_at has just passed, as long as nobody else has taken the seat — and then performs the DB write to mark seats BOOKED.
  • Persist payment record and link to booking.

Late Payment (hold expired and seats were resold)

  • Money was captured, but the seats now belong to someone else.
  • Don't overwrite the other user's seats. Offer the closest equivalent seats if available; otherwise auto-refund immediately and tell the user.
  • To make this rare: extend the hold once when the user reaches the payment page, and make the hold slightly longer than the payment gateway's own timeout.

  1. Failure Path
  • If payment fails or times out, do not confirm booking. Allow TTL to release seats.
  • If payment is captured but DB update fails, Payment Service retries the finalization idempotently.

Idempotency & Safety

  • Use an idempotency key for the entire payment+confirm workflow.
  • Payment Service stores state machine per key: INITIATED → PROCESSING → SUCCESS → NOTIFIED.
  • Re-entrant callbacks detect already-processed keys and no-op.


8) Scaling & Performance (~5 mins)

Partitioning

  • Partition services by show_id (each show independent) so seat state is sharded and hotspots are distributed.

Caching

  • Use Redis for fast read path (availability) and for holds.
  • Cold data (past shows/bookings) persisted in DB, moved to cold storage.

Resilience to traffic spikes

  • Autoscale Inventory Service, use request queuing at API Gateway for surge control.
  • For blockbuster releases, implement pre-book queuing and adaptive rate limits.

Monitoring & SLOs

  • Metrics: holds/sec, confirms/sec, failed payments, Redis ops, DB latency.
  • SLOs: <1s seat availability read, >99.9% booking durability, near-zero double-book.


9) Failure Modes & Recovery

Redis Failure

  • If Redis unavailable: degrade to DB-backed locking (slower but consistent), or apply circuit-breaker to reject holds temporarily.

Payment Gateway Delays

  • Accept asynchronous confirmation; keep holds short; offer “pending” UI feedback.

Partial Writes

  • Reconciliation job: scan HELD entries older than TTL and reconcile with DB.
  • Audit logs for every hold/confirm operation for forensic and retry.


10) Trade-offs & Alternatives

Redis vs DB locking

  • Redis provides very low latency and atomic multi-key Lua scripts — ideal for seats. DB locking is more durable but slower.

Synchronous vs Asynchronous Booking Persist

  • Persisting synchronously on payment confirmation ensures strong durability. Asynchronous persist risks temporary inconsistencies but scales writes.

Seat Hold TTL length

  • Short TTL reduces lock contention but risks user UX friction (time to complete payment). Choose ~3–5 minutes, tuned by analytics.


11) Security & Data Integrity

  • Use TLS for all endpoints; secure payment callbacks with signatures.
  • Validate user identity before allowing holds.
  • Audit logs of holds and confirmations; immutable payment records.


12) Interview Time Allocation (45 min)

  • 5 min: Requirements & scope
  • 10 min: HLD & architecture diagram
  • 5 min: Data model & flows
  • 10 min: Scaling & failure handling
  • 15 min: Deep dives (Seat locking + Payments)


13) Summary

  • Core of BookMyShow is safe seat allocation under high concurrency and integrating payment semantics reliably.
  • Use Redis for low-latency holds (atomic Lua scripts + TTL), DB for durable booking records, and Payment Service with idempotency for safe external interactions.
  • Partition by show_id, autoscale services, and prepare reconciliation jobs for failure recovery.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →