The problem
"Design a movie ticket booking system." People open the showtimes for their city and a date, pick seats on a map of the screen, hold them while they pay, and get a ticket to scan at the door. The cinemas keep selling the same seats at their own box offices.
This practice problem reuses the pattern of Design Ticketmaster. What's new: many small showings instead of one huge on-sale, a second seller at the box office, listings read far more than seats change, and a cinema's system that can stop answering. Park snacks, memberships and loyalty points.
The numbers
From the interviewer: about 4,000 cinemas, 5 screens each, 5 showings a day on every screen, about 200 seats a showing. Round a day to 100,000 seconds (86,400 really).
- Seat maps: 4,000 × 5 × 5 = 100,000 showings a day, each its own map: about 20 million seat rows a day, one per seat per showing.
- Tickets: say 5 million a day, a quarter of the seats: 50 a second on average, about 250 a second at busy hours (5×).
Ticketmaster's on-sale put about 33,000 holds a second on one event's 50,000 seats. Here the load is steady and spread thin: a hold touches one 200-seat map, so on a normal day no map is a hot spot.
Two sellers, one seat map
Riverside Cinema sells at its box office from its own ticketing system, and your app sells the same seats online. Keeping your own seat map and syncing it with the cinema's every minute lets two places decide:
time box office your app G7
18:40:10 sells G7 not synced one ticket
18:40:40 – sells G7 two tickets
18:41:00 sync sync two owners, one seat
A sync copies what happened; it can't stop two sales inside its gap. Splitting the seats, rows A to F online and the rest at the desk, never sells a seat twice, but on a busy Friday one half sells out while the other sits empty.
The common design: the cinema's system decides. Your booking service asks it to hold G7 and G8; it runs the same conditional update for its box office and for you, holds them for 10 minutes and answers with its hold id. After payment, you ask it to turn the hold into a sale. Your platform keeps only a copy of each seat map, refreshed every few seconds, to draw it. If a cinema sells at its desk through your platform instead, your seats table decides: one place decides, and the other asks it. The no-gaps rule many cinemas use, never leaving a single empty seat between two bookings, is checked in that same place, before the hold.
Listings, copies and live calls
Most visits start with a city's showtimes for a date, and most never book. Schedules are set days ahead and rarely change, so each city and date page is read thousands of times per change:
- Cache for hours: the showtimes, each film's poster and rating, the prices. Rebuild a city and date page when a cinema edits its schedule.
- A copy seconds old: the seat map while you pick, and the seats left next to each showtime, so a sold-out showing doesn't look open all evening.
- Ask the cinema live: holds and sales, the only calls that change a seat.
GET /showtimes?city=boston&date=2026-10-30
→ 200 OK [{: "s42", : "19:00"}, …]
POST /holds {: "s42", : ["G7", "G8"]}
→ 201 Created {: "h9", : "10 min"}
POST /orders {: "h9", : "pm_31", : "k4"}
→ 201 Created {: "o12"}
When a cinema's system is down
Friday at 18:50, Riverside's system stops answering, and every hold times out. Selling from your copy would sell seats twice, as the box office keeps selling, and retrying every hold every second keeps a struggling system down.
Use a circuit breaker, as in Timeouts, Retries and Circuit Breakers: after a few timeouts in a row, stop calling Riverside for a short while, show "online booking paused" for its showings at once, then let one hold through to test whether it's back. Every other cinema keeps selling. A sale call that times out after the card was charged is retried with the same idempotency key while the hold still runs; if the hold is gone, the charge is refunded.
Blockbuster openings
A blockbuster's opening-night on-sale is not steady: say 1 million fans in 10 minutes, about 1,700 a second, 7× a busy evening, most aimed at the same few showings and so at a few cinemas' systems.
Turn a waiting room on for that film's on-sale only. Its fans wait on a light page with a place in line and are let in at the rate the cinemas' systems can take, each with a signed token; everyone booking another film goes straight through. A line in front of every showing would only make each booking wait: Ticketmaster needs one because an on-sale brings 40 fans for every seat, while a Tuesday showing has seats to spare. More booking servers don't help, because the cinemas' systems decide the seats.
Same pattern as Ticketmaster
A hold is one conditional update, taken only if the seat is free or its hold has run out, so two people tapping G7 at once get one hold and one "taken". Paying checks the hold before any money moves, and the order carries an idempotency key. The map is a copy; the hold decides.
Summary
- Scope: showtimes, a seat map, 10-minute holds, pay; the box office sells too.
- Data: one row per seat per showing, about 200 each; 100,000 showings a day.
- Services: the cinema's system decides each seat; your platform keeps a copy to show.
- Scale: listings cached by city and date; seat maps from copies seconds old.
- Cinema down: a circuit breaker; never sell from the copy.
- Openings: a waiting room only for blockbuster on-sales.