Facebook Pixel

Design a Movie Ticket Booking System

A movie ticket booking system models physical objects — cinemas, shows, and seats — each with state that must stay consistent across concurrent operations, the hallmark of a real-world-entity problem like a parking lot or a hotel. The defining challenge is seat allocation: at any moment, a seat is either free or booked, and two users trying to book the same seat simultaneously must not both succeed.

See It in Action

The demo below runs the finished allocation logic for one show: tap free seats to select them, then book them. Already-booked seats are rejected — the core invariant the rest of the article builds toward. The grey seats were booked by earlier customers.

Movie Ticket Booking

Clarifying Requirements

Good first questions narrow the scope to a buildable design.

Does the system manage multiple shows simultaneously? Yes — a cinema runs several shows at once, each in a different screen. Are seats numbered? Yes, each show assigns fixed seat numbers starting at 1. Can a booking be cancelled? Yes. Do we need pricing or payment? No — this design covers availability and allocation only. What happens when a seat is already booked and someone tries to book it again? Return an error message; do not overwrite.

The functional requirements for this walkthrough: a Cinema holds multiple shows identified by a show ID; each show has a fixed number of seats numbered 1 through N; a show command creates a show; a book command books a specific seat for a named user, failing if the seat is already taken or out of range; a cancel command releases a booked seat; an available command reports how many free seats remain.

The two primary flows — booking and cancellation — start at the caller and each return a single string result.

Core Entities

Scanning the requirements for nouns that carry state yields three objects.

Seat owns the central invariant: it holds at most one booking at a time. Its state is a seat number (immutable) and the name of the user who booked it (None when free). Its behavior is is_free, book, and cancel. Nothing outside Seat writes its booking field directly — the invariant lives here.

Show represents one screening. It owns a collection of seats indexed by seat number and exposes book, cancel, and available. The show is responsible for validating that a seat number is in range before delegating to the seat. This keeps range-checking in one place: Show.book asks "is this seat number valid?" and then tells the seat what to do — it does not expose the seat map to callers.

Cinema is the facade. It maps show IDs to shows and routes each command to the right show. It is the only object the command dispatcher talks to; Show and Seat are internal implementation details. The cinema is also responsible for detecting duplicate show creation.

The prompt revolves around "bookings," so a Booking object looks like an obvious fourth entity. Before reading on, decide whether a booking earns its own class here, or whether its state belongs somewhere simpler.

Decision checkpoint

1)

Booking and cancelling are the core operations. Should a Booking be its own value object?

So booking state lives on the Seatbooked_by is the booking — and no separate Booking class is introduced.

Designing the Classes

Step 3 of the delivery framework derives each class's state from specific requirements, then its behavior as method signatures — before writing any implementation. The principle is Tell, Don't Ask: a class owns decisions about its own data.

Seat

The requirement "a seat is either free or booked by one user" maps to a single nullable field: booked_by: str | None. That one field drives all three methods. is_free tests whether the field is None. book(user) writes it. cancel clears it. No caller reads booked_by directly — callers ask is_free() and receive yes or no.

The seat's number is immutable after construction; it is needed only for building the response string in the show's book and cancel methods. Keeping it on the seat avoids passing the number as a separate parameter through the call chain.

Booking a seat involves two distinct checks: that the seat number is in range, and that the seat is actually free. The Seat owns its own free/booked state. But which class should validate that the seat number exists at all? Before reading on, decide where the range check belongs.

Decision checkpoint

1)

A book request can fail two ways: the seat number is out of range, or the seat is already taken. Which class checks range, and which checks vacancy?

Show

The requirement "seats are numbered 1 through N" determines the data structure: a dictionary from integer seat number to Seat. A list would also work, but a dictionary makes the existence check (seat_num not in self.seats) explicit rather than relying on an index bound.

Three behaviors follow directly from the commands. book(seat_num, user) must check existence, then check vacancy, then delegate — in that order. cancel(seat_num) checks existence, then checks that the seat is actually booked, then delegates. available() counts free seats. The show never exposes its seat dictionary; callers can only ask it questions.

Cinema

The requirement "multiple shows each with a unique ID" gives the state: shows: dict[str, Show]. add_show guards against duplicate IDs. Each of book, cancel, and available first looks up the show by ID and returns an error string if not found, then forwards the request to the show.

The cinema's public interface matches the four commands exactly. Nothing else is public. A small public surface is easier to test and harder to misuse.

Try It Yourself

Before reading the implementation, build it yourself. Implement run_cinema(instructions) that replays a command stream and returns one result line per command.

Each instruction is a list of strings. Four commands are supported:

  • ["show", showId, seats] — create a show with the given number of seats (numbered 1 to N); return "Show <showId>: <seats> seats". If the show ID already exists, return "Show <showId> exists".
  • ["book", showId, seat, user] — book the seat for the user; return "Booked seat <seat> for <user>". Errors: "Unknown show <showId>", "No seat <seat>", "Seat <seat> taken".
  • ["cancel", showId, seat] — release the seat; return "Cancelled seat <seat>". Errors: "Unknown show <showId>", "Seat <seat> not booked".
  • ["available", showId] — return "Available: <count>". Error: "Unknown show <showId>".
Invest in Yourself
Your new job is waiting. 83% of people that complete the program get a job offer. Unlock unlimited access to all content and features.
Go Pro