Before you start
BookMyShow-style movie booking is a rich design problem in one subtle way: atomicity. Everything else is bookkeeping.
The atomicity signal
hold(seats) must reserve ALL seats or NONE.
Half-holds are a disaster — a caller thinks they got 5
seats but actually got 3 and lost 2 to a race. In a
real production system this leads to "yeah bro I booked
5 seats but only 3 showed up in my confirmation".
Enforcement pattern: two-phase check-then-mutate:
def hold(self, show_id, seats, ...):
# Phase 1: check ALL seats are available. Raise if any fails.
for r, c in seats:
if not self._seat_available(show_id, r, c):
raise ValueError(f"seat {(r,c)} not available")
# Phase 2: mutate. Guaranteed to succeed.
for r, c in seats:
self._mark_held(show_id, r, c)
If phase 1 raises, no state changes. If phase 1 passes, phase 2 runs cleanly. Not thread-safe (that's a lock or DB transaction), but atomic within a single call — which is what MVP needs.
The state question
Each seat has 3 states: available, held, booked. Transitions:
- available → held (via hold)
- held → booked (via confirm)
- held → available (via release or expiry)
- booked → nothing (no cancellation in MVP)
Do you use the State pattern (module 3.14) per seat? Almost certainly no — 3 states × N seats = ceremony without payoff. A single status field per seat plus one mutation helper does fine.
But the TRANSITIONS still need to be in ONE place. If
confirm and release both write seat.status = ... directly, bugs will drift. Route both through a
helper.
The expiry model
Real systems use wall-clock time + background sweeps. For interviews, you sidestep threading by making the caller drive time:
bs.hold("s1", [(0,0)], "alice", hold_seconds=60)
bs.tick(now=1000) # nothing expires
bs.tick(now=99999) # everything old expires
Your design.md should acknowledge this is a simplification and mention what a production system would do differently (event loop, DB TTL, background thread).
The rules
Three matter here:
- No seat is both held AND booked. Enforced by the seat's status field being one value at a time.
- Hold is atomic (all seats or none). Enforced by the two-phase pattern above.
- Expired hold can't be confirmed. Enforced by
confirm()checking the hold's status is still "active" before promoting to "booked".
Name each enforcement point in design.md.
Interviewer follow-ups to prepare for
- "How would you handle two users trying to hold the same seat at the same time?" — locks / DB row-level lock / optimistic concurrency. Out of MVP scope; mention.
- "How would you scale to millions of seats and thousands of requests per second?" — HLD territory. Sharded seat inventory, event-driven cache invalidation. Also out of scope.
- "How would you allow cancellations?" — new state (cancelled) + refund flow. Manageable extension if your design is clean.