Design a Movie Ticket Booking System
OOP design for movie ticket booking covering show scheduling, seat selection with locking, payment processing, and concurrent booking handling.
The Problem
Your company runs a cinema chain with 30 screens across 5 locations. The current booking system is a basic web form that checks seat availability at the moment of submission. Last Friday night, the opening weekend for a blockbuster, 14 customers received confirmation emails for seats that had already been sold to someone else. Front desk had to turn away families who had driven 40 minutes for a show that was "confirmed" in their inbox.
Movie ticket booking is fundamentally a concurrent seat reservation problem. Unlike hotel rooms (one guest per room per night), a cinema screen has hundreds of seats where dozens of users might be selecting seats for the same show simultaneously. The core challenge is preventing two users from booking the same seat while still allowing a smooth selection experience. Nobody wants to pick 4 seats, enter payment details, and then discover those seats are gone.
Design the core classes for a movie ticket booking system that handles show scheduling across multiple screens, seat selection with temporary locking, payment processing, and concurrent booking prevention.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to turn the vague prompt into a concrete specification. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "Does the system manage multiple cinemas, or are we designing for a single location?"
Interviewer: "Multiple cinemas, each with multiple screens. But focus the class design on one cinema's booking flow. The multi-location aspect is just data separation."
Good. That tells you Cinema is an entity but the interesting logic lives at the Show and Seat level.
You: "Are there different seat categories with different prices?"
Interviewer: "Yes. Regular, premium, and VIP. Each has a different base price, and pricing can also vary by time of day and day of week."
Multiple seat types with time-based pricing. That is two dimensions of pricing variation, a strong signal for Strategy pattern.
You: "When a user selects seats, are they immediately reserved, or is there a hold period for payment?"
Interviewer: "There should be a temporary hold. The user gets 8 minutes to complete payment. If they don't pay, the seats release back to available."
Temporary hold with TTL. This is the crux of the concurrency problem. You need a seat locking mechanism with automatic expiry.
You: "What happens if two users try to select the same seat at the exact same moment?"
Interviewer: "Only one should get the lock. The other should see those seats as unavailable."
Atomic lock acquisition. The system needs to handle race conditions at the seat lock level.
You: "Do we need to support cancellations and refunds?"
Interviewer: "Yes. Full refund if more than 2 hours before showtime, 50% refund between 30 minutes and 2 hours, no refund after that."
Time-based refund tiers. Another Strategy pattern candidate, or a simple policy method.
You: "Should the system notify users when their lock is about to expire or when a booking is confirmed?"
Interviewer: "Yes, send notifications on booking confirmation and cancellation. The notification channel can vary: email, SMS, push."
Multiple notification channels. That is Observer pattern territory.
You: "Are food and beverage add-ons part of the booking, or a separate system?"
Interviewer: "Out of scope for now, but the design should make it easy to add later."
Perfect. You have now clarified scope and identified the key design challenges: seat locking with TTL, concurrent access handling, pluggable pricing, and cancellation policies.
Final Requirements
Functional Requirements:
- Browse available shows for a movie at a specific cinema and screen
- View seat map with real-time availability (available, locked by others, booked)
- Select and temporarily lock seats for a configurable hold period (default 8 minutes)
- Process payment and confirm booking atomically
- Cancel bookings with time-based refund calculation
- Release expired seat locks automatically
Non-Functional Requirements:
- Thread safety for concurrent seat locking (no double bookings)
- Extensibility for new seat types, pricing models, and notification channels
- Clean separation between booking logic, payment, and notification concerns
Out of Scope:
- UI rendering and seat map visualization
- Persistence layer and database queries
- Food and beverage add-ons (designed for easy addition later)
- User authentication and session management
30-Second Design Summary
Model availability per show, not per physical seat: Seat describes layout, SeatLock temporarily claims a seat for a user, and Booking records the confirmed reservation. BookingService coordinates show lookup, locking, payment, confirmation, cancellation, and notifications; pricing and refunds are strategies. The central invariant is that a seat moves from available to locked or booked through an atomic show-scoped operation, with an expiry check before confirmation.
5-Minute Walkthrough
- Set the scope. The core covers shows, seat maps, temporary holds, payment confirmation, refunds, and expired-lock cleanup. Persistence, UI, authentication, and add-ons are extension points.
- Separate static and contextual state.
Movie,Screen, andSeatare mostly static;Showgives the screening context;SeatLockandBookingcarry per-show occupancy. - Lock seats. Validate the requested seats belong to the show and are not booked or held by an unexpired lock, then create locks with an expiry time and token in one atomic operation.
- Confirm payment. Re-check the token and expiry, charge or authorize payment, convert the locks to a booking, and release the hold. If payment is uncertain, keep the state recoverable and avoid treating an unverified charge as success.
- Release and extend. A sweeper removes expired locks, cancellation applies a refund strategy, and new seat types, pricing rules, channels, or durable lock stores can be added behind existing interfaces.
Example Inputs and Outputs
Scenario 1: Successful Booking
- Input: User selects seats A1, A2 for "Inception" at 7:00 PM on Screen 3
- Step 1: System checks availability, both seats are AVAILABLE
- Step 2: System locks both seats for 8 minutes, returns lock token
- Step 3: User completes payment within the hold period
- Expected: Booking confirmed, seats move to BOOKED, confirmation notification sent
Scenario 2: Concurrent Conflict
- Input: User A and User B both try to lock seat B5 for the same show at the same moment
- Expected: One user gets the lock (first to acquire), the other receives "seat no longer available"
- Why: Atomic lock acquisition prevents double booking
Scenario 3: Lock Expiry
- Input: User locks seats C3, C4, abandons checkout without paying
- Expected: After 8 minutes, locks expire automatically, seats return to AVAILABLE
- Why: TTL-based locks prevent seats from being held indefinitely
Scenario 4: Cancellation with Partial Refund
- Input: User cancels a confirmed booking 1 hour before showtime
- Expected: Booking cancelled, 50% refund issued, seats return to AVAILABLE
- Why: Falls in the 30min-2hr refund tier
Try It Yourself
Try it yourself
Before reading the solution, spend 15-20 minutes sketching your own class diagram. Focus on the seat state lifecycle and how you would prevent two users from booking the same seat. The locking mechanism is the most interesting part of this problem. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this problem? Look at your requirements and pull out the nouns. A cinema has screens, screens show movies at certain times (shows), shows have seats, users book seats, and bookings involve payments.
A common mistake is lumping the show and the movie together. A Movie is static data (title, duration, genre). A Show is a specific screening of that movie on a particular screen at a particular time. The same movie plays on multiple screens at different times. Separating them lets you reuse movie data across shows.
Another common mistake is putting seat availability logic inside the Seat class. A seat is a physical thing (row A, number 5, premium category). Whether it is available depends on the show. Seat A5 is booked for the 7 PM show but free for the 9 PM show. That availability state belongs to the relationship between a seat and a show, not to the seat itself.
| Entity | Responsibility | Key attributes |
|---|---|---|
| Cinema | Top-level container. Holds screens and location data. | name, location, screens |
| Screen | A physical auditorium with a fixed seat layout. | name, seats, capacity |
| Movie | Static film metadata. Reused across multiple shows. | title, duration, genre, rating |
| Show | A specific screening of a movie on a screen at a time. | movie, screen, startTime, endTime |
| Seat | A physical seat with a fixed position and category. | row, number, seatType |
| SeatLock | Temporary hold tying a seat to a user for a specific show. The core concurrency primitive. | seat, show, user, expiresAt, lockToken |
| Booking | Confirmed reservation linking a user to seats for a show. | user, show, seats, totalAmount, status |
| Payment | Record of a financial transaction tied to a booking. | amount, method, status, transactionId |
| User | The person making the booking. | name, email, phone |
Notice SeatLock is its own entity, not a state on Seat. That is deliberate. A seat's physical properties (row, number, type) never change. Lock state is ephemeral and show-specific. Merging them would mean a Seat object would need to know about shows, users, and time, violating SRP.
Step 2: Define Relationships and Class Design
Class Diagram
Class Interface Derivation
Show
The Show connects a Movie to a Screen at a specific time. It is the central entity that users browse and book against.
Deriving state from requirements:
| Requirement | What Show must track |
|---|---|
| "Browse available shows for a movie" | The movie being screened, screen location, start/end time |
| "View seat map with availability" | Reference to the screen (which has the seat layout) |
| "Shows can be cancelled" | A status field (scheduled, cancelled, completed) |
Deriving methods from needs:
| Need | Method |
|---|---|
| "Calculate end time from movie duration" | Constructor derives endTime from startTime + movie.duration |
| "Check if show is still bookable" | isBookable() returns true if status is SCHEDULED and startTime is in the future |
Booking
The Booking is the main orchestration record. It ties a user's seat selections to a show and tracks the payment lifecycle.
Deriving state from requirements:
| Requirement | What Booking must track |
|---|---|
| "Select and lock seats" | The list of seats being booked |
| "Process payment and confirm" | Payment reference and booking status |
| "Cancel with refund" | Status transitions (PENDING to CONFIRMED to CANCELLED) |
| "Time-based refund tiers" | Creation timestamp and show reference for time calculations |
Deriving methods from needs:
| Need | Method |
|---|---|
| "Confirm after payment" | confirm(payment) transitions from PENDING to CONFIRMED |
| "Cancel a booking" | cancel() transitions to CANCELLED, only from CONFIRMED |
| "Calculate refund" | getRefundAmount(refundPolicy) delegates to a policy object |
We keep the Booking class focused on state management. Pricing calculation, refund logic, and notification dispatch all live in separate service or strategy classes. The Booking does not know how prices are computed or how notifications are sent.
SeatLock
The SeatLock is the concurrency primitive. It represents a temporary hold on a seat for a specific show.
Deriving state from requirements:
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.