MindForge
MindForge
DashboardHelp & Support
Dashboard
MindForge

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Design a Movie Ticket Booking System
Course contents

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Browsing without an account — progress is saved in this browser only.Sign in to save it

Design a Movie Ticket Booking System

Reading55 min

This is the concurrency problem of the section. The class model itself is simple — city, cinema, screen, show, seat, booking — and every interviewer moves within about ten minutes to the one question that actually matters: two people click the same seat at the exact same instant. What happens? Get that right, and everything else is just bookkeeping.

Step 1 — Requirements and the booking lifecycle

What the system needs to do:

  • Search for shows by city, movie, and date.
  • Show a show's seat map, with each seat's current availability.
  • Let a user pick one or more seats and hold them while they pay.
  • Confirm the booking once payment succeeds, or release the hold if it fails or times out.
  • Cancel a booking, following a refund policy.

The question that shapes the entire design: how long can a user hold a seat before actually paying for it? The answer — "a few minutes" — immediately rules out holding a database lock that whole time, and points straight to the reservation-with-a-TTL pattern instead. Say this early. It's really the insight this whole problem is built around.

The other clarifying questions:

Question Answer used here What it changes
Does pricing vary? Yes — by seat class and show time Strategy
Can a user book multiple seats at once? Yes — all or nothing The hold covers a whole set of seats
What if payment fails, or the user just disappears? The hold expires on its own An expiry timestamp, not a boolean flag
Multiple servers? Yes The invariant has to live in the database
Is overbooking ever allowed? Never A hard constraint, not a best-effort guess

The lifecycle is a state machine, and it's worth thirty seconds to draw it:

AVAILABLE ──hold()──▶ HELD ──confirm()──▶ BOOKED ──cancel()──▶ AVAILABLE
    ▲                  │                                          ▲
    └──── expire() ◀────┘   (TTL elapsed, or payment failed) ──────┘

Two things about that diagram are worth points: HELD is a genuine state, not a boolean, and leaving HELD can happen without anyone calling anything at all — the expiry is driven purely by time, which is exactly why the hold carries a timestamp rather than a flag.

Remember: a seat hold needs an expiry timestamp, not a flag — a crashed browser tab has to release the seat automatically, with nobody having to clean up after it.

Sign in to try this knowledge check.

Step 2 — Entities and relationships

Concept Kind of thing Notes
City, Cinema, Screen Entities Cinema ◆── Screen is composition
Movie Entity Exists on its own, independent of any specific show
Show Entity A movie playing on one screen at one time — this is the unit you actually book
Seat Entity Belongs to a screen: a physical row, number, and class
ShowSeat Entity The one people forget: whether a seat is available for one specific show
SeatStatus Enum AVAILABLE / HELD / BOOKED
Booking Entity A user's set of show-seats, with its own lifecycle
PricingStrategy Interface Varies by seat class, show time, day
PaymentProcessor Interface Left out of scope to actually implement
City ◇── Cinema ◆── Screen ◆── Seat              (the physical layout)
                       │
                     Show ──> Movie              (one screening)
                       ◆
                       │ 1..*
                   ShowSeat ──> Seat             (availability, per show)
                       │  - status: SeatStatus
                       │  - held_until: int|None
                       │  - booking_id: str|None
                       ▲
                   Booking ◇── 1..* ShowSeat

Splitting Seat from ShowSeat is the real modelling insight in this problem. A physical seat exists exactly once and never changes. Its availability is a property of the pair (seat, show), not of the seat by itself. If you put is_booked directly on Seat, you can't express the same seat being booked for the 9pm show while still free for the 6pm one — and being able to say that sentence out loud is worth more here than any pattern name.

Remember: availability belongs to the (seat, show) pair — never to the seat alone.

Sign in to try this knowledge check.

Step 3 — The concurrency design

This is what the whole interview is actually about. Build the answer up in three layers.

Layer 1 — the hold is one atomic conditional write. Not "check availability, then separately mark it held." One single statement that both checks and takes at the same time:

sql
UPDATE show_seats
   SET status = 'HELD', held_by = $1, held_until = now() + interval '5 minutes'
 WHERE show_id = $2
   AND seat_id = ANY($3)
   AND (status = 'AVAILABLE'
        OR (status = 'HELD' AND held_until < now()));   -- an expired hold can be reclaimed
-- affected rows < requested count  ⇒  someone else already won  ⇒  roll back, tell the user

That AND clause is the entire concurrency mechanism. The database checks the condition and performs the write as one atomic step, so two concurrent attempts genuinely cannot both succeed. Counting the affected rows is how you find out whether you actually won.

Layer 2 — a multi-seat booking is all-or-nothing. Wrap the update in a transaction, and require the number of affected rows to exactly equal the number of seats requested, or roll everything back. Half a booking is worse than none at all.

Two seats grabbed in a different order by two different users is exactly the deadlock case from the concurrency lesson — sort the seat ids before locking, so every transaction acquires locks in the same fixed order.

Layer 3 — a unique constraint as the final backstop. Even with flawless application code, add this:

sql
CREATE UNIQUE INDEX one_booking_per_seat_per_show
    ON show_seats (show_id, seat_id) WHERE status = 'BOOKED';

Application code can always have bugs. A database constraint cannot be bypassed. Bringing this up yourself is exactly the sign of someone who's actually shipped a booking system before.

Why not just use a Redis lock? You can, but only as an optimisation to cut down contention hitting the database — it can never be the actual guarantee. A garbage-collection pause or a slow network can outlast any TTL, and the lock holder has no way to know it was evicted early. The sentence to say: "a Redis lock reduces contention; the unique constraint is what actually makes it correct."

Expiry: a background sweeper flips expired holds back to AVAILABLE, mostly to keep the seat map tidy — but the held_until < now() clause already built into the hold query means correctness never actually depends on that sweeper running at all.

Remember: only the database can arbitrate between two different servers. A Redis lock helps with load; the unique constraint is what makes it actually correct.

Sign in to try this knowledge check.

Step 4 — The implementation

An in-memory model that reproduces the exact same behaviour, with the conditional-write logic made explicit so it maps directly onto the SQL above:

Python
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
import itertools
import threading


class SeatClass(Enum):
    REGULAR = 1
    PREMIUM = 2
    RECLINER = 3


class SeatStatus(Enum):
    AVAILABLE = "available"
    HELD = "held"
    BOOKED = "booked"


class BookingStatus(Enum):
    PENDING = "pending"
    CONFIRMED = "confirmed"
    CANCELLED = "cancelled"
    EXPIRED = "expired"


@dataclass(frozen=True)
class Seat:                                   # physical, never changes
    seat_id: str
    row: str
    number: int
    seat_class: SeatClass


@dataclass
class ShowSeat:                               # availability, specific to one show
    seat: Seat
    status: SeatStatus = SeatStatus.AVAILABLE
    held_until: int | None = None
    booking_id: str | None = None

    def is_takeable(self, now: int) -> bool:
        """AVAILABLE, or a HELD seat whose hold has expired — mirrors the SQL WHERE clause."""
        if self.status is SeatStatus.AVAILABLE:
            return True
        return self.status is SeatStatus.HELD and self.held_until is not None \
            and self.held_until <= now


class PricingStrategy(ABC):
    @abstractmethod
    def price_paise(self, seat: Seat, show: "Show") -> int: ...


class ClassAndTimePricing(PricingStrategy):
    BASE = {SeatClass.REGULAR: 20_000, SeatClass.PREMIUM: 35_000, SeatClass.RECLINER: 60_000}

    def price_paise(self, seat, show):
        price = self.BASE[seat.seat_class]
        if show.start_hour >= 18:             # an evening surcharge
            price = int(price * 1.2)
        return price


@dataclass
class Show:
    show_id: str
    movie: str
    screen_id: str
    start_hour: int
    seats: dict[str, ShowSeat] = field(default_factory=dict)


@dataclass
class Booking:
    booking_id: str
    user: str
    show_id: str
    seat_ids: list[str]
    total_paise: int
    status: BookingStatus = BookingStatus.PENDING


class BookingService:
    HOLD_SECONDS = 300

    def __init__(self, pricing: PricingStrategy):
        self._pricing = pricing
        self._shows: dict[str, Show] = {}
        self._bookings: dict[str, Booking] = {}
        self._ids = itertools.count(1)
        self._lock = threading.Lock()     # stands in for what the database's atomicity gives us

    def add_show(self, show: Show) -> None:
        self._shows[show.show_id] = show

    def available_seats(self, show_id: str, now: int) -> list[str]:
        show = self._shows[show_id]
        return sorted(sid for sid, ss in show.seats.items() if ss.is_takeable(now))

    def hold(self, show_id: str, seat_ids: list[str], user: str, now: int) -> Booking:
        show = self._shows[show_id]
        ordered = sorted(seat_ids)            # a fixed order avoids deadlock on multi-seat holds
        with self._lock:
            targets = [show.seats[sid] for sid in ordered]
            if not all(ss.is_takeable(now) for ss in targets):
                taken = [ss.seat.seat_id for ss in targets if not ss.is_takeable(now)]
                raise RuntimeError(f"seats no longer available: {taken}")   # all or nothing

            booking = Booking(
                booking_id=f"B{next(self._ids)}", user=user, show_id=show_id,
                seat_ids=ordered,
                total_paise=sum(self._pricing.price_paise(ss.seat, show) for ss in targets),
            )
            for ss in targets:                # commit the hold
                ss.status = SeatStatus.HELD
                ss.held_until = now + self.HOLD_SECONDS
                ss.booking_id = booking.booking_id
            self._bookings[booking.booking_id] = booking
            return booking

    def confirm(self, booking_id: str, now: int) -> Booking:
        with self._lock:
            booking = self._bookings[booking_id]
            if booking.status is not BookingStatus.PENDING:
                raise ValueError(f"booking is {booking.status.value}")
            show = self._shows[booking.show_id]
            for sid in booking.seat_ids:
                ss = show.seats[sid]
                if ss.booking_id != booking_id or (ss.held_until or 0) <= now:
                    booking.status = BookingStatus.EXPIRED     # the hold lapsed before payment
                    raise RuntimeError("hold expired; seats released")
            for sid in booking.seat_ids:      # payment succeeded — make it permanent
                ss = show.seats[sid]
                ss.status, ss.held_until = SeatStatus.BOOKED, None
            booking.status = BookingStatus.CONFIRMED
            return booking

    def cancel(self, booking_id: str) -> None:
        with self._lock:
            booking = self._bookings[booking_id]
            show = self._shows[booking.show_id]
            for sid in booking.seat_ids:
                ss = show.seats[sid]
                ss.status, ss.held_until, ss.booking_id = SeatStatus.AVAILABLE, None, None
            booking.status = BookingStatus.CANCELLED


def build_show() -> tuple[BookingService, Show]:
    seats = {f"A{i}": ShowSeat(Seat(f"A{i}", "A", i, SeatClass.PREMIUM)) for i in range(1, 5)}
    show = Show("S1", "Dune", "SCR1", start_hour=19, seats=seats)
    svc = BookingService(ClassAndTimePricing())
    svc.add_show(show)
    return svc, show


svc, show = build_show()

# --- the happy path: hold, then confirm --------------------------------------
b1 = svc.hold("S1", ["A2", "A1"], user="asha", now=0)
assert b1.seat_ids == ["A1", "A2"]                     # sorted, so ordering is always consistent
assert b1.total_paise == 2 * int(35_000 * 1.2)         # premium seats, evening surcharge
assert svc.available_seats("S1", now=0) == ["A3", "A4"]

# --- a second user can't take a seat that's already held ---------------------
try:
    svc.hold("S1", ["A1"], user="ravi", now=10)
    raise AssertionError("double hold allowed")
except RuntimeError as e:
    print("rejected:", e)

svc.confirm(b1.booking_id, now=60)
assert show.seats["A1"].status is SeatStatus.BOOKED

# --- all-or-nothing: A3 is free but A1 is booked, so the whole request fails --
try:
    svc.hold("S1", ["A3", "A1"], user="ravi", now=70)
    raise AssertionError("partial booking allowed")
except RuntimeError:
    pass
assert show.seats["A3"].status is SeatStatus.AVAILABLE   # A3 was NOT taken either

# --- an abandoned hold expires, and the seat becomes takeable again ----------
b2 = svc.hold("S1", ["A3"], user="meera", now=100)
assert svc.available_seats("S1", now=200) == ["A4"]              # still held
assert svc.available_seats("S1", now=100 + 301) == ["A3", "A4"]  # the hold lapsed
try:
    svc.confirm(b2.booking_id, now=100 + 301)
    raise AssertionError("expired hold confirmed")
except RuntimeError as e:
    print("expiry enforced:", e)

print("final statuses:", {sid: ss.status.value for sid, ss in show.seats.items()})
Sign in to try this knowledge check.

Steps 5 and 6 — Edge cases and extensions

Edge cases worth raising before anyone asks:

Case How it's handled
Payment succeeds, but the hold already expired by then Never book over someone else's seat. Refund automatically and tell the user. Even better: extend the hold the moment payment starts, and make confirmation idempotent on a payment id
A user pays twice by double-clicking An idempotency key on the booking request — the second attempt just returns the first result
The seat map looks stale in the browser Show an optimistic update in the UI, but let the server's conditional write make the real decision
The cinema cancels the show Bulk-cancel bookings and trigger refunds through an Observer, or an event — never a loop inside the show object itself
A group wants seats next to each other A SeatAllocationStrategy that's aware of adjacency — the exact same extension point the parking lot's allocator used
Refund amount depends on how early someone cancels A CancellationPolicy strategy that returns the refundable amount
A hugely popular show, thousands of holds at once A virtual waiting room plus per-user rate limits; the conditional write is still what guarantees correctness — it just gets rejected more often

Extensions, and how they fit in:

  • Coupons and offers → a DiscountPolicy combined with pricing (or a decorator layered on top of PricingStrategy).
  • Food and drink add-ons → line items on the Booking; pricing itself is unchanged.
  • Loyalty points → an observer on booking.confirmed.
  • Multiplex chains, multiple cities → new entities added above Cinema; the booking core itself doesn't change.
  • Rendering the seat map → a read-only view built from ShowSeat, never a second source of truth.

The closing line worth having ready: "The core of this design is a per-show seat table, a hold with an expiry, and one conditional write. Everything else — pricing, discounts, refunds, notifications — hangs off that as either a strategy or an observer."

Remember: never let a lapsed hold win against a correctly-timed one, even when the customer already paid. Refund automatically, and fix the root cause: align the hold window with the payment window.

Sign in to try this knowledge check.

Quick recap

  • This problem is about concurrency, not classes. Bring up the seat-contention question yourself, rather than waiting for the interviewer to ask.
  • Seat vs. ShowSeat is the real modelling insight — availability belongs to the (seat, show) pair.
  • A hold with an expiry, not a lock: a real person takes minutes to pay, and no lock should ever be held that long. The expiry is what makes abandonment heal itself.
  • One conditional write is the whole mechanism, backed up by a unique index. In-process locks and Redis locks are optimisations; the database constraint is the actual guarantee.
  • All-or-nothing bookings for multiple seats need both a transaction and a consistent seat ordering, to avoid deadlock.
  • This same skeleton solves every inventory problem you'll ever meet — flight seats, hotel rooms, event tickets, e-commerce stock, appointment slots. Learn it once, right here.
PreviousDesign an Elevator SystemNextDesign an Expense-Sharing App (Splitwise)

Course progress

0%
0 of 177 modules complete

On this page

  • Step 1 — Requirements and the booking lifecycle
  • Step 2 — Entities and relationships
  • Step 3 — The concurrency design
  • Step 4 — The implementation
  • Steps 5 and 6 — Edge cases and extensions
  • Quick recap