List the key functional requirements for the system (Ask the AI for hints if stuck)...
List the key non-functional requirements (performance, scalability, reliability, etc.)...
Seat inventory requires strong consistency.
If 1 seat exists and 1,000 users click it simultaneously, exactly one user should successfully acquire it.
The browsing/search side should be highly available.
A temporary failure in recommendations or analytics should not prevent ticket purchases.
The system should horizontally scale because traffic is highly uneven.
Target:
Payment and ticket issuance must be idempotent.
If the payment provider sends the same webhook three times, we must not create three tickets.
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
Let's assume:
500,000 orders / 86,400 seconds
≈ 5.8 orders/sec
But averages are misleading.
Suppose peak traffic is 100x average:
5.8 × 100 ≈ 580 orders/sec
For a major movie release, we could potentially see thousands of purchase attempts per second.
The important point is:
The read traffic is much larger than the write traffic.
For example:
Search/showtime/seat reads: ~1000x
Purchases: relatively small
Therefore:
Read path → CDN/cache/read replicas
Write path → strongly consistent transactional database
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
GET /movies?query=batman
GET /movies/{movieId}
GET /movies/{movieId}/shows?date=2026-08-15
GET /shows/{showId}/seats
Response:
{
"showId": "show_123",
"seats": [
{
"id": "A1",
"row": "A",
"number": 1,
"status": "AVAILABLE"
},
{
"id": "A2",
"row": "A",
"number": 2,
"status": "HELD"
}
]
}
POST /shows/{showId}/holds
{
"seatIds": ["A1", "A2"]
}
Response:
{
"holdId": "hold_123",
"expiresAt": "2026-08-13T16:35:00Z"
}
POST /orders
{
"showId": "show_123",
"holdId": "hold_123"
}
POST /orders/{orderId}/payment
POST /payments/webhook
The webhook should be idempotent.
GET /orders/{orderId}/ticket
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
┌──────────────┐
│ Client │
│ Web / Mobile │
└──────┬───────┘
│
▼
┌──────────────┐
│ CDN / WAF │
└──────┬───────┘
│
▼
┌──────────────┐
│ API Gateway │
└──────┬───────┘
│
┌────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Movie/Search│ │ Seat Service │ │ Order │
│ Service │ │ │ │ Service │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│ Redis │ │ Redis │ │Postgres│
└───────┘ └───────┘ └───────┘
│ │
│ ▼
│ ┌──────────┐
│ │ Payment │
│ │ Provider │
│ └──────────┘
│
▼
┌───────────┐
│ Event Bus │
│ Kafka │
└─────┬─────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
Notification Analytics Ticket
Service Service Service
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
I'd use PostgreSQL for the transactional system.
Why?
Because ticket purchasing requires:
users
-----
id
email
name
created_at
movies
id
title
description
duration
rating
venues
id
name
location
seats
id
venue_id
row
seat_number
shows
id
movie_id
venue_id
start_time
end_time
This is extremely important.
show_seats
id
show_id
seat_id
status
price
hold_id
hold_expires_at
order_id
The same physical seat can be available for one show and sold for another.
So inventory is really:
(show_id, seat_id)
not simply:
seat_id
I'd add:
UNIQUE(show_id, seat_id)
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
Suppose:
User A → A10
User B → A10
User C → A10
all arrive simultaneously.
We cannot do:
SELECT status
FROM show_seats
WHERE seat_id = 'A10';
if status == AVAILABLE:
UPDATE status = HELD;
because two requests can read:
AVAILABLE
before either updates the row.
That's a race condition.
We can use:
BEGIN;
SELECT *
FROM show_seats
WHERE show_id = ?
AND seat_id = ?
FOR UPDATE;
The first transaction locks the row.
Then:
Request A
↓
locks A10
↓
checks AVAILABLE
↓
changes → HELD
↓
COMMIT
Request B
↓
waits for lock
↓
gets row
↓
sees HELD
↓
fails
Therefore only one request succeeds.
This is a strong answer in an interview.
I'd use a state machine:
AVAILABLE
│
│ hold
▼
HELD
│
├──────────────┐
│ │
payment expiration
succeeds │
│ ▼
▼ AVAILABLE
SOLD
The important rule:
A HOLD is temporary. SOLD is permanent.
For example:
A10 → AVAILABLE
User selects A10
A10 → HELD
expires_at = now + 5 minutes
Payment succeeds
A10 → SOLD
If payment doesn't happen:
5 minutes later
A10 → AVAILABLE
There are two common approaches.
A worker periodically scans:
SELECT *
FROM show_seats
WHERE status = 'HELD'
AND hold_expires_at < NOW();
and releases them.
But there's an important problem.
We don't want to scan millions of rows continuously.
We could use:
Redis sorted set
where:
score = expiration timestamp
member = hold ID
Then workers process expired holds.
However, the database remains the source of truth.
Redis is used for efficient expiration scheduling, not as the final authority for whether a seat was sold.
This is another area I'd emphasize.
Client
│
▼
Create Hold
│
▼
Create Order
│
▼
Payment Provider
│
▼
Payment succeeds
│
▼
Webhook
│
▼
Order Service
│
▼
Mark order PAID
│
▼
Mark seats SOLD
│
▼
Issue ticket
Because the browser isn't trustworthy.
The client saying:
"Payment succeeded"
is not enough.
We should trust the payment provider's server-to-server notification.
Imagine the payment provider sends:
payment_succeeded
three times.
Without idempotency:
Webhook #1 → ticket created
Webhook #2 → another ticket
Webhook #3 → another ticket
Bad.
Instead:
payment_events
provider_event_id UNIQUE
When the webhook arrives:
if event_id already processed:
return 200
This makes processing idempotent.
Similarly:
POST /orders
Idempotency-Key: abc123
allows a client to retry without accidentally creating multiple orders.