Airline reservation system
LLD of an airline reservation system covering flight inventory, cabin classes, overbooking strategy, fare rules, booking with payment, and involuntary denied boarding handling.
The Problem
Your airline handles 400 flights per day across 80 routes. The legacy reservation system tracks seat availability with a single counter per flight. When demand spikes for holiday routes, the counter goes negative because two agents sell the last seat simultaneously. Last Thanksgiving, 14 passengers arrived at the gate for 12 remaining seats and nobody knew who should be denied boarding.
Airline reservation is harder than hotel or movie booking because of overbooking. Airlines deliberately sell more seats than physically exist because a percentage of passengers always cancel or no-show. The system must track availability at the cabin and fare-class level, enforce overbooking limits, calculate fares with multiple pricing factors, manage the booking lifecycle from hold through boarding, and handle the ugly reality of involuntary denied boarding when too many passengers actually show up.
Design the core classes for an airline reservation system that handles flight inventory by cabin, fare-class-level seat availability, overbooking strategy, fare calculation, booking with payment, check-in with seat assignment, and denied boarding resolution.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to narrow the problem. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "What cabin classes does the system support? Are they fixed or configurable per aircraft?"
Interviewer: "Three cabin classes: Economy, Business, and First. Each cabin has a fixed seat count per aircraft type, but different aircraft can have different counts."
Three cabins as an enum. The seat count is tied to the aircraft, not hardcoded globally.
You: "Are we modeling seat selection where passengers pick specific seats like 14A, or just tracking availability counts per cabin?"
Interviewer: "Both. Track availability counts for booking, but also maintain a seat map for seat selection during check-in. Passengers can optionally select a seat at booking time too."
That means two levels of inventory: aggregate counts for availability checks, and a per-seat map for assignment. I will separate these concerns.
You: "Is overbooking in scope? If so, how is the overbooking limit determined?"
Interviewer: "Yes, overbooking is critical. The airline should support multiple overbooking strategies: a fixed percentage over capacity, a demand-based model, and a no-overbook option for premium routes."
Multiple overbooking strategies with runtime selection. That is a Strategy pattern signal. This is the core differentiator from simpler booking systems.
You: "How does fare calculation work? Flat rate per cabin, or something more complex?"
Interviewer: "Fares depend on cabin class, a base fare, demand surge multiplier, and optional add-ons like extra baggage. The calculation should be pluggable so the airline can swap pricing models."
Another Strategy pattern candidate. Fare calculation is a composition of factors, not a single formula.
You: "What happens when a flight is overbooked and all passengers show up at the gate?"
Interviewer: "The system must handle involuntary denied boarding. Pick the passengers to bump based on configurable criteria: last to check in, lowest fare paid, or no loyalty status. Offer compensation."
Denied boarding resolution is a Strategy as well. Three strategies for selecting who gets bumped.
You: "Does the booking go through a lifecycle? Hold, confirmed, checked-in, and so on?"
Interviewer: "Yes. A booking starts as HOLD when the fare is locked. It moves to CONFIRMED after payment, then CHECKED_IN at the airport, then BOARDED when the passenger physically boards. It can be CANCELLED from HOLD or CONFIRMED states only."
Five states with constrained transitions. That is a State pattern for the booking lifecycle.
You: "Should check-in follow a specific flow, or is it just a status change?"
Interviewer: "Check-in has a defined sequence: validate the booking is confirmed, assign a seat if not already selected, generate a boarding pass. The steps are always the same but some details vary by cabin class."
A fixed sequence with varying steps per cabin. That maps to the Template Method pattern for the check-in flow.
You: "Are multi-leg itineraries in scope? Like NYC to LAX with a connection in Dallas?"
Interviewer: "Not for the initial design. Single-segment flights only. Multi-leg is a good extensibility discussion."
Perfect. You have clarified the core scope and identified four pattern signals: Strategy (overbooking, fares, denied boarding), State (booking lifecycle), and Template Method (check-in).
Final Requirements
Functional Requirements:
- The system manages flights with three cabin classes (Economy, Business, First), each with configurable seat counts per aircraft type
- Passengers search for available seats by flight, cabin, and passenger count, respecting overbooking limits
- Bookings move through a defined lifecycle: HOLD, CONFIRMED, CHECKED_IN, BOARDED, CANCELLED
- Fare calculation uses pluggable strategies combining base fare, cabin multiplier, demand surge, and baggage add-ons
- Overbooking limits are configurable per flight using interchangeable strategies (fixed percentage, demand-based, none)
- Check-in validates the booking, assigns a seat, and generates a boarding pass in a fixed sequence
- When an overbooked flight has more passengers than seats, the system selects passengers for denied boarding using configurable criteria
Non-Functional Requirements:
- Concurrent booking attempts for the same fare class must be handled safely (synchronized seat decrement)
- Adding a new overbooking strategy, fare model, or denied-boarding policy requires a new class, not changes to existing code
- The booking state machine enforces valid transitions only (no jumping from HOLD to BOARDED)
Out of Scope:
- Multi-leg itineraries and connections (extensibility discussion only)
- Loyalty program and frequent flyer tiers (extensibility discussion only)
- Payment gateway internals (use an interface)
- UI rendering and mobile apps
- Persistence / database layer
- Passenger authentication
- Flight scheduling and crew management
Example Inputs and Outputs
Scenario 1: Successful booking
- Input: Search flight AA100 (NYC to LAX), Economy cabin, 2 passengers
- System: Economy has 180 seats, 170 confirmed bookings, 5% overbooking limit (189 max). Available: 19 seats. Returns fare options.
- Input: Select "Economy Saver" fare at $250/person, provide payment token
- Expected: Booking created with PNR "XK7M2P", status HOLD. After payment capture, status moves to CONFIRMED. Total: $560 (base $500 + $60 taxes).
Scenario 2: Overbooking rejection
- Input: Search flight AA200, Business cabin, 1 passenger
- System: Business has 24 seats, 25 confirmed bookings (overbooking limit is 26 via demand-based strategy). Available: 1 seat.
- Input: Another agent simultaneously tries to book the last seat
- Expected: One booking succeeds, the other gets a "no availability" error. The synchronized decrement prevents double-booking.
Scenario 3: Check-in and denied boarding
- Input: Passenger checks in for flight AA300 with PNR "RT5N8W"
- System: Validates booking is CONFIRMED. Assigns seat 22C (auto-assign for Economy). Generates boarding pass. Status moves to CHECKED_IN.
- Later: Flight has 180 seats but 185 checked-in passengers. Denied boarding strategy selects 5 passengers (lowest fare, no loyalty) and offers $800 compensation each.
Try It Yourself
Try it yourself
Before reading the solution, spend 20 minutes sketching your entities and their relationships. The key challenge is separating availability tracking (aggregate counts) from seat assignment (individual seats). Think about where overbooking logic lives and how the booking lifecycle enforces valid transitions. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by pulling nouns from your requirements. Every requirement sentence contains at least one entity hiding in plain sight. I like to underline the nouns and then ask: "Does this thing have its own lifecycle, state, or responsibility?"
The airline domain has more entities than a typical LLD problem because the booking process touches inventory, pricing, and operations. A common mistake is lumping everything into a single Flight god class. Good design means each class owns exactly one responsibility.
| Entity | Responsibility | Key attributes |
|---|---|---|
| Flight | Represents a scheduled departure. Connects origin to destination on a specific date. | flightNumber, origin, destination, departure, aircraft |
| Aircraft | Defines physical seat capacity per cabin. Different aircraft types have different layouts. | model, cabinConfig (seats per cabin) |
| CabinClass | Enum distinguishing Economy, Business, and First. Drives pricing and seat assignment rules. | ECONOMY, BUSINESS, FIRST |
| SeatInventory | Tracks aggregate available seats per cabin per flight. The gatekeeper for overbooking math. | totalSeats, bookedCount, overbookingLimit |
| Seat | Individual physical seat on an aircraft. Owns its assignment status and location. | seatNumber, cabin, status (AVAILABLE, HELD, OCCUPIED) |
| Fare | Pricing for a specific cabin on a specific flight. Calculated by a pluggable strategy. | baseFare, cabin, calculatedTotal |
| Booking | The reservation itself. Holds passengers, fare, and lifecycle state. Identified by a PNR. | pnr, passengers, fare, status |
| Passenger | A person on the booking. Carries identity info and seat assignment. | firstName, lastName, passportNumber, seatAssignment |
| BoardingPass | Generated at check-in. Proof of seat assignment with gate and boarding group info. | passenger, seat, gate, boardingGroup |
| Payment | Records the financial transaction for a booking. Modeled as an interface. | amount, status, transactionId |
Notice how SeatInventory and Seat are separate. SeatInventory deals with "how many seats are left" (aggregate math for availability checks). Seat deals with "which specific seat is this passenger sitting in" (individual assignment). Merging them into one class forces availability checks to iterate every seat on the plane, which is unnecessary and slow.
For your interview: list these entities on the whiteboard, draw boxes around the ones that have state machines (Booking, Seat), and circle the ones that are pure data (Passenger, BoardingPass). This gives the interviewer a clear mental model before you start drawing relationships.
Step 2: Define Relationships and Class Design
Part 1: Class Diagram
Part 2: Deriving State and Methods
Booking (the orchestrator)
The Booking is the central entity. It owns the lifecycle, connects passengers to a flight, and coordinates with payment. Every action in the system either creates, modifies, or queries a Booking.
Deriving state from requirements:
| Requirement | What Booking must track |
|---|---|
| "Bookings move through HOLD, CONFIRMED, CHECKED_IN, BOARDED, CANCELLED" | Current lifecycle state |
| "Passengers search by flight and cabin" | The flight and cabin this booking is for |
| "Fare calculation uses pluggable strategies" | The calculated fare for this booking |
| "Booking identified by PNR" | A unique 6-character PNR code |
| "Multiple passengers per booking" | List of passengers |
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Move from HOLD to CONFIRMED after payment" | confirm(Payment) |
| "Check-in validates and assigns seat" | checkIn() |
| "Can be CANCELLED from HOLD or CONFIRMED" | cancel() |
| "Passengers board the aircraft" | board() |
| "Query current state" | getStatus() |
The booking state machine is the most critical piece. Invalid transitions (HOLD to BOARDED, CANCELLED to CHECKED_IN) must be rejected. I will use the State pattern here so each state object defines which transitions it allows.
SeatInventory (the availability gatekeeper)
SeatInventory exists to answer one question fast: "Can we accept N more passengers in this cabin on this flight?" It uses simple arithmetic, not seat-by-seat scanning.
Deriving state from requirements:
| Requirement | What SeatInventory must track |
|---|---|
| "Configurable seat counts per aircraft" | Total physical seats in this cabin |
| "Overbooking limits" | Maximum bookable seats (physical + overbook buffer) |
| "Concurrent booking safety" | Current booked count (must be thread-safe) |
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Check availability for N passengers" | hasAvailability(int count) |
| "Decrement safely under concurrency" | decrementSync(int count) |
| "Release seats on cancellation" | increment(int count) |
The decrementSync method is where thread safety lives. Two agents checking availability simultaneously must not both succeed when only one seat remains. I will use synchronized to make this atomic.
Booking State Machine
The booking lifecycle has five states with constrained transitions. This diagram shows every valid path:
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.