1. Users should be able to search for movies playing in their location.
2. Users should be able to view nearby theaters and available showtimes.
3. Users should be able to view the seat map and current seat availability for a selected show.
4. Users should be able to temporarily reserve/hold one or more seats while completing checkout.
5. Users should be able to purchase movie tickets.
6. Users should receive a booking confirmation after successful payment.
7. Users should be able to view their existing bookings.
The most important functional requirement is preventing double booking. Two users should never be able to successfully purchase the same seat for the same show.
Out of Scope:
- Movie reviews and ratings
- Recommendation engine
- Loyalty/rewards program
- Theater administration portal
- Refunds and cancellations
Non-Functional Requirements:
1. Scalability
Assume the system serves the North American market with approximately 300 million potential users.
Assume 10% are daily active users:
300M × 10% = 30M DAU
A user normally generates multiple requests before completing a booking, such as:
- Search movies
- Search theaters
- View showtimes
- View seats
- Refresh seat availability
- Reserve seats
- Create booking
- Make payment
Assume approximately 20 API requests per active user per day.
30M × 20 = 600M requests/day
Average QPS:
600,000,000 / 86,400 ≈ 7,000 QPS
Traffic will be higher during evenings, weekends, and major movie releases.
Assuming approximately 5-10x peak traffic:
Peak QPS ≈ 35,000-70,000 requests/second.
I would therefore design the read-heavy system for approximately 50K+ peak QPS with horizontal scaling.
2. Booking QPS
If up to 30M bookings happened per day:
30,000,000 / 86,400 ≈ 350 booking requests/second average.
Assuming a 10x peak:
~3,500 booking requests/second.
I would design the reservation and booking system for roughly 5K booking operations/second.
3. Latency
Target latency:
- Movie search: < 200 ms p95
- Theater/showtime lookup: < 200 ms p95
- Seat availability: < 300 ms p95
- Seat reservation: < 500 ms p95
- Booking creation: < 500 ms excluding external payment processing
4. Consistency
Different parts of the system require different consistency guarantees.
Eventual consistency is acceptable for:
- Movie search
- Movie metadata
- Theater information
- Showtime discovery
- Analytics
Strong consistency is required for:
- Seat reservation
- Seat availability during checkout
- Booking confirmation
- Payment state
For the reservation system, consistency is more important than availability because double booking must never happen.
5. Availability
Target:
- Search/Browsing: 99.99%
- Booking/Reservation: 99.9%+
For browsing, availability is more important than strong consistency.
For seat reservations and bookings, consistency is more important than availability.
6. Storage / Capacity
Assume approximately 5 million completed bookings per day.
If each booking and related metadata consumes approximately 1 KB:
5M × 1 KB ≈ 5 GB/day
5 GB × 365 ≈ 1.8 TB/year
Including indexes, replicas, reservation history, payment metadata, audit logs, and analytics data, the system may require several TB of storage per year.
Movie, theater, and showtime metadata is relatively small compared with booking, event, and analytics data.
I would expose REST APIs through an API Gateway.
Core APIs:
1. Search Movies
GET /v1/movies?city={city}&date={date}
Example:
GET /v1/movies?city=Austin&date=2026-08-30
Response:
[
{
"movieId": "movie123",
"title": "Example Movie",
"duration": 120,
"rating": "PG-13"
}
]
2. Get Showtimes
GET /v1/movies/{movieId}/showtimes?city={city}&date={date}
Example:
GET /v1/movies/movie123/showtimes?city=Austin&date=2026-08-30
Response:
[
{
"showId": "show789",
"theaterId": "theater456",
"theaterName": "AMC Downtown",
"startTime": "19:30"
}
]
3. Get Seat Availability
GET /v1/shows/{showId}/seats
Response:
[
{
"seatId": "A7",
"status": "AVAILABLE",
"price": 15
},
{
"seatId": "A8",
"status": "BOOKED",
"price": 15
}
]
4. Hold / Reserve Seats
POST /v1/holds
Request:
{
"showId": "show789",
"seatIds": ["A7", "A8"]
}
Response:
{
"holdId": "hold123",
"status": "HELD",
"expiresAt": "2026-08-30T19:05:00Z"
}
The hold should expire after approximately 5 minutes if payment is not completed.
5. Create Booking
POST /v1/bookings
Request:
{
"holdId": "hold123",
"paymentMethodId": "paymentXYZ"
}
The client should also send an Idempotency-Key header.
Example:
Idempotency-Key: booking-user123-hold123
This prevents duplicate bookings or duplicate payment charges if the client retries the request.
Response:
{
"bookingId": "booking456",
"status": "CONFIRMED",
"totalAmount": 30
}
6. Get Booking
GET /v1/bookings/{bookingId}
Response:
{
"bookingId": "booking456",
"movie": "Example Movie",
"theater": "AMC Downtown",
"showTime": "19:30",
"seats": ["A7", "A8"],
"status": "CONFIRMED"
}
API Gateway responsibilities:
- Authentication
- Authorization
- Rate limiting
- Request validation
- Routing
- Load balancing
- Logging and monitoring
The system uses a service-oriented architecture where read-heavy operations such as movie search and showtime discovery are separated from strongly consistent reservation and booking operations.
Clients communicate with the platform through a CDN/WAF and API Gateway.
The API Gateway routes requests to specialized services:
1. Search Service
- Handles movie and theater discovery.
- Uses Redis and a search engine such as Elasticsearch/OpenSearch.
2. Showtime Service
- Returns movie schedules, theaters, and show information.
- Uses Redis for caching and SQL for persistent catalog data.
3. Reservation Service
- Handles seat availability and temporary seat holds.
- Uses a strongly consistent relational database.
- Prevents two users from reserving the same seat.
4. Booking Service
- Converts a valid seat hold into a confirmed booking.
- Coordinates with the payment service.
5. Payment Service
- Integrates with an external payment provider.
- Uses idempotency to prevent duplicate charges.
6. Message Queue
- Publishes events after successful booking.
- Used for asynchronous operations such as notifications and analytics.
7. Notification Service
- Sends email, SMS, or push notifications after booking confirmation.
The application services are stateless and can be horizontally scaled behind load balancers.
I would use a relational SQL database such as PostgreSQL/MySQL/Aurora as the primary transactional database.
SQL is a good fit because the booking workflow requires:
- ACID transactions
- Strong consistency
- Unique constraints
- Foreign key relationships
- Atomic updates
- Row-level locking
The most important data entities are:
1. User
User
----
user_id PK
name
phone
created_at
2. Movie
Movie
-----
movie_id PK
title
description
duration
rating
release_date
3. Theater
Theater
-------
theater_id PK
name
address
city
state
latitude
longitude
4. Screen
A theater may have multiple auditoriums/screens.
Screen
------
screen_id PK
theater_id FK
name
capacity
Relationship:
Theater 1 ---- N Screen
5. Seat
Seat
----
seat_id PK
screen_id FK
row_number
seat_number
seat_type
Examples of seat_type:
STANDARD
PREMIUM
RECLINER
6. Show
Show
----
show_id PK
movie_id FK
screen_id FK
start_time
end_time
Relationships:
Movie 1 ---- N Show
Screen 1 ---- N Show
7. ShowSeat
This is one of the most important tables.
A physical seat can have a different availability state for every show.
ShowSeat
--------
show_id PK/FK
seat_id PK/FK
status
hold_id
hold_expires_at
price
version
Composite Primary Key:
(show_id, seat_id)
Possible statuses:
AVAILABLE
HELD
BOOKED
8. Hold
Hold
----
hold_id PK
user_id FK
show_id FK
status
expires_at
created_at
Possible statuses:
ACTIVE
EXPIRED
COMPLETED
9. Booking
Booking
-------
booking_id PK
user_id FK
show_id FK
hold_id FK
total_amount
status
created_at
Possible booking statuses:
PENDING
CONFIRMED
FAILED
CANCELLED
10. BookingSeat
BookingSeat
-----------
booking_id FK
seat_id FK
price
11. Payment
Payment
-------
payment_id PK
booking_id FK
provider_transaction_id
amount
status
created_at
Possible statuses:
PENDING
SUCCESS
FAILED
REFUNDED
Database Relationships:
User 1 ---- N Booking
Movie 1 ---- N Show
Theater 1 ---- N Screen
Screen 1 ---- N Seat
Screen 1 ---- N Show
Show 1 ---- N ShowSeat
Booking 1 ---- N BookingSeat
Booking 1 ---- 1 Payment
Database Scaling:
Initially, I would use a primary SQL database with read replicas.
Writes such as:
- Seat holds
- Bookings
- Payments
go to the primary database.
Read replicas can serve read-heavy requests where slightly stale data is acceptable.
At larger scale, the Seat Inventory database can be partitioned/sharded using show_id because reservation operations for a particular movie showing naturally belong together.
Example:
hash(show_id) % number_of_shards
I would deep dive into three important components:
1. Seat Reservation Service
2. Booking and Payment Service
3. Search and Caching Layer
==================================================
1. SEAT RESERVATION SERVICE
==================================================
The Seat Reservation Service is the most critical component because it must prevent double booking.
Each seat for a particular show has one of three states:
AVAILABLE -> HELD -> BOOKED
If the user does not complete payment within the hold period:
HELD -> AVAILABLE
For example:
AVAILABLE
|
| User selects seat
v
HELD
|
+---- Payment succeeds ----> BOOKED
|
+---- Hold expires --------> AVAILABLE
When a user selects seats A7 and A8, the Reservation Service performs an atomic transaction.
Conceptually:
BEGIN TRANSACTION
SELECT *
FROM show_seat
WHERE show_id = ?
AND seat_id IN ('A7', 'A8')
FOR UPDATE
The service checks that all seats are AVAILABLE.
If they are available, it updates:
status = HELD
hold_id = generated_hold_id
hold_expires_at = current_time + 5 minutes
COMMIT
Because the rows are locked inside the transaction, another transaction trying to reserve the same seats cannot successfully reserve them simultaneously.
If two users attempt to reserve seat A7 at exactly the same time, only one request should succeed.
Pessimistic Locking:
We can use:
SELECT ... FOR UPDATE
Advantages:
- Easy to reason about
- Strong consistency
- Prevents double booking
Disadvantages:
- Lock contention
- Reduced concurrency for very popular shows
Optimistic Locking:
Another option is adding a version field to ShowSeat.
Example:
seat_id = A7
status = AVAILABLE
version = 5
Update:
UPDATE show_seat
SET status = 'HELD',
version = 6
WHERE show_id = ?
AND seat_id = ?
AND status = 'AVAILABLE'
AND version = 5
If rows affected = 1:
Reservation succeeded.
If rows affected = 0:
Another user modified/reserved the seat first.
Optimistic locking can provide better throughput but requires retry/error handling.
Hold Expiration:
Seat holds should last approximately 5 minutes.
A background worker periodically finds expired holds and changes:
HELD -> AVAILABLE
Another implementation option is maintaining hold expiration entries in Redis using TTLs while keeping SQL as the source of truth.
==================================================
2. BOOKING AND PAYMENT SERVICE
==================================================
The Booking Service converts a valid seat hold into a confirmed booking.
Typical flow:
1. User reserves seats.
2. Seats become HELD.
3. Booking Service creates a PENDING booking.
4. Payment Service calls the external payment provider.
5. If payment succeeds:
- Booking becomes CONFIRMED.
- Seats become BOOKED.
6. If payment fails:
- Booking becomes FAILED.
- Seats are released back to AVAILABLE.
7. A booking event is published to the message queue.
8. Notification workers send the ticket/confirmation asynchronously.
Booking state machine:
PENDING -> CONFIRMED
PENDING -> FAILED
Payment state:
PENDING -> SUCCESS
PENDING -> FAILED
Idempotency:
Payment and booking APIs should support idempotency.
For example, the client sends:
Idempotency-Key: booking-user123-hold456
If the user's network times out and the application retries the request, the server checks whether the same idempotency key was already processed.
If it was processed successfully, the server returns the existing booking instead of charging the customer again.
This prevents duplicate bookings and duplicate payments.
Distributed Transaction Problem:
The payment provider is an external system, therefore we cannot rely on one traditional database transaction covering both our database and the payment provider.
Instead, the booking process should use a state machine / saga-like workflow.
For example:
Seat HELD
->
Booking PENDING
->
Payment Request
->
Payment SUCCESS
->
Booking CONFIRMED
->
Seat BOOKED
If payment fails:
Payment FAILED
->
Booking FAILED
->
Seat AVAILABLE
==================================================
3. SEARCH AND CACHING
==================================================
Most application traffic will be read traffic rather than booking traffic.
Typical high-volume requests include:
- Search movies
- Find theaters
- View showtimes
- View movie information
- View seat maps
These operations can tolerate eventual consistency.
The Search Service can use Elasticsearch/OpenSearch for fast movie and theater searches.
Redis can cache frequently accessed data such as:
- Popular movies
- Theater information
- Showtime listings
- Seat availability snapshots
Example cache keys:
movie:movie123
theater:theater456
show:show789
show:show789:seats
Request flow:
Client
->
API Gateway
->
Search/Showtime Service
->
Redis
If cache hit:
Return result immediately.
If cache miss:
Read from database/search index
->
Return result
->
Populate Redis
Redis reduces load on the primary databases and significantly improves read latency.
However, Redis should not be considered the authoritative source for confirmed seat bookings.
The SQL Seat Inventory database remains the source of truth.
==================================================
SCALING
==================================================
Application services are stateless and can be horizontally scaled behind a load balancer.
For example:
Reservation Service
Instance 1
Instance 2
Instance 3
...
Instance N
Database Scaling:
1. Add read replicas for read-heavy operations.
2. Use indexes on commonly queried fields.
3. Partition/shard seat inventory by show_id.
4. Cache frequently accessed metadata in Redis.
5. Use OpenSearch/Elasticsearch for search workloads.
A natural shard key for seat inventory is:
show_id
because most reservation requests operate on seats belonging to one specific show.
Example:
hash(show_id) % number_of_shards
==================================================
ASYNC PROCESSING
==================================================
Operations that do not need to block the user request should be processed asynchronously.
After a successful booking, the Booking Service publishes an event to Kafka, RabbitMQ, SQS, or another message queue.
Consumers can independently process:
- Email notification
- SMS notification
- Push notification
- Analytics
- Audit logging
Example:
Booking Service
|
v
Message Queue
/ \
Notification Analytics
Worker Worker
This reduces booking latency and prevents notification failures from affecting the main booking transaction.
==================================================
KEY TRADEOFFS
==================================================
1. SQL vs NoSQL
I prefer SQL for seat inventory and bookings because these operations require ACID transactions and strong consistency.
NoSQL or search databases may still be used for read-heavy or search workloads.
2. Strong vs Eventual Consistency
Strong consistency:
- Seat reservation
- Booking
- Payment
Eventual consistency:
- Movie metadata
- Search
- Theater information
- Analytics
3. Consistency vs Availability
For movie browsing:
Availability > Strong Consistency
For seat booking:
Consistency > Availability
It is better to temporarily reject a booking request than accidentally sell the same seat twice.
4. Redis vs Database
Redis improves latency and reduces database load.
However:
Redis = Cache / Optimization
SQL = Source of Truth
==================================================
MAIN BOTTLENECKS
==================================================
1. Popular movie releases creating sudden traffic spikes.
Mitigation:
- Horizontal scaling
- Rate limiting
- CDN
- Redis
- Waiting-room/queue mechanism if necessary
2. High contention for the same seats.
Mitigation:
- Row-level locking
- Optimistic concurrency control
- Atomic conditional updates
3. Database overload.
Mitigation:
- Caching
- Read replicas
- Database indexes
- Sharding/partitioning
4. Payment provider failures.
Mitigation:
- Idempotency
- Retries with exponential backoff
- Payment webhooks
- State-machine based booking workflow
5. Notification failures.
Mitigation:
- Message queue
- Retry mechanism
- Dead-letter queue