← All notes

Systems & Cloud

System Design Interview: Designing Seat Booking for BookMyShow

System Design Interview: Designing Seat Booking for BookMyShow

A common system design interview question is:

“How would you design a platform like BookMyShow?”

The full system has many parts: movie discovery, event listings, showtimes, seat selection, payments, ticket generation, notifications, refunds, and analytics.

But in an interview, one of the most important areas to go deep on is seat booking.

Why?

Because seat booking has one hard rule:

Never double-book a seat.

Everything else is secondary.


Quick Infographic

Article content

infographic - System Design of Bookmyshow - by Vinay C


1. Start with the core principle

For seat booking, I would optimize for correctness first, scale second.

A user may tolerate a slightly stale seat map.

But a user should never pay for a seat that was also sold to someone else.

So the design principle is:

Database = source of truth Cache = fast read layer

Redis can help with speed, temporary holds, and seat-map reads.

But the database should make the final decision on whether a seat is available, held, or booked.


2. Basic tech stack

For the booking flow, I would use:

Backend: Java, Kotlin, or Go Database: PostgreSQL, MySQL, or Aurora Cache: Redis / Redis Cluster Queue: Kafka, SQS, or similar Services: Stateless services behind load balancers

The core services would be:

  • Seat Map Read Service
  • Booking Service
  • Payment Service
  • Ticket Service
  • Notification Service

The booking service is the most sensitive part because it controls seat ownership.


3. Data model

A simplified data model could look like this:

shows
- show_id
- movie_id / event_id
- venue_id
- start_time

show_seats
- show_id
- seat_id
- status: AVAILABLE / HELD / BOOKED
- hold_id
- locked_until
- version

bookings
- booking_id
- user_id
- show_id
- status: INITIATED / PAYMENT_PENDING / CONFIRMED / FAILED / EXPIRED

booking_seats
- booking_id
- seat_id

payments
- payment_id
- booking_id
- status 

The most important table is show_seats.

This is where the system tracks the real state of every seat for every show.


4. Seat hold flow

When a user selects seats, I would not immediately mark them as booked.

Instead, I would use a temporary hold model.

Flow:

  1. User selects seats.
  2. Booking service tries to hold those seats.
  3. Seats are marked as HELD for 5–10 minutes.
  4. User completes payment.
  5. Seats move from HELD to BOOKED.
  6. If payment fails or time expires, seats become available again.

The hold operation should be atomic.

Example:

UPDATE show_seats
SET status = 'HELD',
    hold_id = ?,
    locked_until = now() + interval '5 minutes'
WHERE show_id = ?
  AND seat_id IN (...)
  AND (
    status = 'AVAILABLE'
    OR (status = 'HELD' AND locked_until < now())
  ); 

After this, check the affected row count.

If all requested seats were updated, the hold succeeded.

If not, at least one seat was unavailable, so the hold fails.

This prevents two users from holding the same seat at the same time.


5. Payment success flow

When payment succeeds, the system should again update the database first.

UPDATE show_seats
SET status = 'BOOKED'
WHERE show_id = ?
  AND hold_id = ?
  AND status = 'HELD'
  AND locked_until > now(); 

Only if this succeeds should the booking become confirmed and the ticket be generated.

This is important because payment callbacks can be delayed, retried, duplicated, or arrive after the hold has expired.

So the system must be idempotent.


6. Where Redis fits

Redis is useful, but Redis should not be the final authority for seat ownership.

I would use Redis for:

  • Fast seat-map reads
  • Short-lived seat status cache
  • Temporary coordination
  • Per-show rate limiting
  • Hot show protection

I would not use Redis as the only source of truth for final bookings.

A good mental model is:

DB = correctness
Redis = speed / user experience 

When a user opens the seat map:

  1. Try Redis first.
  2. On cache miss, load from DB.
  3. Store in Redis with a short TTL.
  4. Return the seat map.

If Redis is stale, that is acceptable.

A user may see a seat as available, click it, and then get:

“Sorry, this seat is no longer available.”

That is much better than double-booking a paid seat.


7. Should DB and cache always be in sync?

No.

I would not design the system assuming Redis and DB are perfectly synchronized at every millisecond.

Instead, I would design Redis as a derived view.

After the DB commits a change, publish an event:

SeatHeld
SeatBooked
SeatReleased 

Consumers update Redis.

Also use short TTLs so stale entries naturally expire.

The goal is not perfect sync.

The goal is eventual convergence, with the database always making the final booking decision.


8. Scaling for India-level traffic

Assume:

  • 1,00,000 live users
  • 5,000 shows running right now
  • 1,00,000 shows active for booking

The important partition key is show_id.

Why?

Because seat conflicts happen inside a show.

All seats for a show should live together.

show_id → shard_id 

This helps avoid distributed transactions.

For database scaling:

  • Use sharded SQL databases.
  • Partition by show_id.
  • Keep all seats for one show on one shard.
  • Partition old data by time and move it to cold storage.
  • Keep current and future shows in hot storage.

For cache scaling:

Use Redis Cluster.

Redis is still an in-memory cache, but Redis Cluster means multiple Redis nodes act as one logical cache.

Example:

Redis Node 1 → seat_status:show_1
Redis Node 2 → seat_status:show_2
Redis Node 3 → seat_status:show_3 

So we are not dependent on one giant Redis box.

We can distribute data across many nodes and add replicas for failover.


9. Cache size thinking

We do not need to cache everything as large JSON.

Separate layout from status:

seat_layout:{screen_id} → mostly static, long TTL
seat_status:{show_id}  → dynamic, compact 

If we have:

100,000 shows
300 seats per show
= 30 million seat statuses 

That sounds huge, but if represented compactly, it is manageable.

For example, seat status can be stored using bitmaps:

AVAILABLE seats bitmap
HELD seats bitmap
BOOKED seats bitmap 

Even with Redis overhead, this may be in the range of a few GB to tens of GB, depending on representation.

A production setup may use:

6 Redis primary nodes
6 Redis replica nodes
16 GB each 

The exact size depends on traffic, representation, TTL, and how many shows are cached.

Also, we should mostly cache active, upcoming, and hot shows, not old shows.


10. Hot show problem

The hardest problem is not 1,00,000 shows.

The hardest problem is one blockbuster show.

Example:

50,000 users want the same show
Only 300 seats exist
Everyone is refreshing the seat map
Everyone is trying to hold the same seats 

This creates a contention problem.

I would handle this in layers.

First, serve seat-map reads from Redis only.

Do not let every refresh hit the DB.

Second, use per-show rate limiting:

show_id:123 → max 500 hold attempts / second 

This prevents one hot show from hurting the rest of the platform.

Third, use a waiting room or token system for extreme demand.

Users enter a queue and receive a short-lived booking token when it is their turn.

Only users with valid tokens can attempt seat hold.

Fourth, for extremely hot shows, route booking attempts through a per-show queue.

booking_queue:{show_id} 

A small number of workers process hold attempts for that show.

This reduces pressure on the database and avoids thousands of concurrent updates on the same few rows.

But even here, the DB still makes the final decision.


11. Final interview summary

If I were answering this in an interview, I would summarize it like this:

For seat booking, I would use a relational database as the source of truth because we need ACID guarantees, row-level locking, and strong consistency.

Redis would be used for fast seat-map reads, temporary status caching, and rate limiting, but not as the final authority.

The booking flow would use temporary holds. Seats move from AVAILABLE to HELD to BOOKED. Holds expire after a few minutes if payment does not complete.

At scale, I would shard the database by show_id, use Redis Cluster for seat-map cache, keep services stateless, and use Kafka or SQS for async workflows like ticket generation and notifications.

For blockbuster shows, I would add per-show rate limits, waiting rooms, booking tokens, and possibly per-show queues to protect the database.

The most important design principle is:

A stale seat map is acceptable. A double-booked seat is not.

Originally published on LinkedIn.