Compile Ready
All low level design problems
Low Level Design/Advanced/Core Systems

Design an Airline Reservation System

Flights, fares, seat inventory, PNRs, and a booking/cancellation workflow with concurrency control.

Advanced 60m interview 17m read Medium frequency Popularity 73
State Strategy Observer Repository Amazon Oracle Airbnb

Problem Statement

Design the object model for an airline reservation system where travelers can search flights, inspect available seats by cabin class, place a temporary seat hold, confirm the booking into a PNR, and cancel or expire the booking when the lifecycle demands it.

The core interview challenge is not a full airline GDS. It is the seat inventory and booking lifecycle: one seat must never be sold twice, a held seat must be locked for one booking for a bounded time, pricing should vary by cabin class, and every booking should move through an explicit state machine.

Business context

Airline Reservation is a high-signal LLD question because the domain has real business pressure: inventory is scarce, demand changes quickly, and a bad concurrency decision directly causes overbooking. Interviewers use it to test whether a candidate can separate search from reservation, model a PNR as a lifecycle object, isolate pricing policy, and explain the critical section around seat holds and confirmation.

Functional Requirements

  • Represent flights with origin, destination, departure time, and a finite set of seats.

  • Represent seats with a seat number, cabin class, and inventory status such as available, held, or booked.

  • Search flights by route, travel date, and requested cabin class while showing only flights with matching availability.

  • Allow a traveler to select a concrete seat and place a time-bound hold on it.

  • Create an itinerary/PNR for the held seat, passenger, flight, and lifecycle status.

  • Confirm a held booking by pricing the selected seat and converting the hold into a booked seat.

  • Cancel confirmed or held bookings and release the seat back to inventory when appropriate.

  • Expire stale holds so seats do not remain blocked forever.

Non-Functional Requirements

Inventory correctness

A seat can be owned by at most one active booking. Hold and confirm are the critical operations and must be guarded atomically.

Low-latency search

Search should scan indexed flight data and read current inventory quickly; it must not take locks longer than needed for a consistent seat snapshot.

Explicit lifecycle

Booking transitions must be visible and constrained. Draft, held, confirmed, cancelled, and expired states should not be represented by scattered booleans.

Extensible pricing

Cabin-class fares, demand adjustments, loyalty discounts, and promotions should vary independently from the reservation flow.

Observable events

Hold, confirm, cancel, and expire events should be publishable to email, SMS, analytics, and downstream inventory systems.

Requirement Clarification

QDo we need to integrate with external airline inventory providers?

No for the base design. Treat flights and seats as in-memory domain objects. A repository or adapter can later replace the in-memory list.

QCan a user book multiple passengers or multiple flight legs in one PNR?

The base model books one passenger on one flight and one selected seat. Multi-passenger and multi-leg PNRs are extensions that wrap several seat allocations in one itinerary.

QShould a hold reserve a cabin class or a specific seat?

Use specific seat selection because the prompt emphasizes seat inventory management. Holding a class-level quota is a useful alternative design but less concrete for LLD.

QHow long does a seat hold last?

Assume a configurable hold duration, such as ten minutes. Confirmation must reject expired holds and release their seats.

QIs payment processing in scope?

Only the pricing and confirmation boundary are in scope. A real payment gateway would be an adapter called before the booking is marked confirmed.

UML Class Diagram

Rendering diagram…
Flight owns seat inventory, Booking owns lifecycle, ReservationService orchestrates the booking boundary, and PricingStrategy plus BookingObserver provide policy and event seams.

Sequence Diagram

Rendering diagram…
Search reads inventory, hold locks a specific seat for one PNR, confirmation prices and books the same held seat, and observers receive lifecycle events.

Entity Identification

Flight

Aggregate for one scheduled flight. It owns the seat map and exposes inventory operations such as available seats, hold, confirm, and release.

flightNumberorigindestinationdepartureTimeseats

Seat

Represents a concrete seat such as 2A. It carries cabin class and guarded inventory state, including which booking owns a hold and when that hold expires.

seatNumbercabinClassstatusheldByBookingIdholdExpiresAt

Booking

The PNR/itinerary object. It links passenger, flight, seat, charged amount, and the current state object that controls lifecycle transitions.

idpassengerNameflightseatstatechargedAmountCents

BookingState

State interface implemented by draft, held, confirmed, cancelled, and expired states. Each state decides which transitions are legal.

statusholdconfirmcancelexpireIfNeeded

PricingStrategy

Policy seam for computing fares. The sample class-based strategy uses cabin multipliers and a small demand adjustment.

price(flight, seat)

SearchService

Read-side service that filters flights by route, date, cabin class, and current availability without creating bookings.

search

ReservationService

Application boundary for holds, confirmations, cancellations, expiry sweeps, PNR storage, and observer notification.

bookingspricingStrategyobserversholdDuration

BookingObserver

Event listener interface for side effects such as emails, SMS, analytics, and inventory feeds.

onBookingEvent

Design Patterns Used

State

BookingState prevents illegal lifecycle jumps. A cancelled booking cannot be confirmed, an expired hold cannot be booked, and the rules live in state classes rather than scattered conditionals.

Strategy

PricingStrategy makes fare calculation swappable. Cabin multipliers, demand adjustments, loyalty pricing, and promotions can be added without changing reservation orchestration.

Observer

BookingObserver lets confirmation emails, SMS, analytics, and downstream inventory systems subscribe to booking events without coupling those systems to the domain model.

Factory Method

ReservationService.createBooking centralizes PNR creation. Subclasses can create specialized bookings for loyalty, corporate travel, or multi-leg itineraries while keeping the reservation workflow stable.

Step-by-Step Design

  1. 1Separate search from reservation

    Search is a read path over flights and current cabin availability. It should never create a PNR or mutate seat state; mutation starts only when the traveler selects a concrete seat.

  2. 2Make Seat the inventory lock owner

    The seat object owns available, held, and booked transitions. Hold and book are synchronized so the check-and-change sequence is atomic for one seat.

    public synchronized boolean hold(String bookingId, Instant expiresAt, Instant now) {
        expireHoldIfNeeded(now);
        if (status != SeatStatus.AVAILABLE) {
            return false;
        }
        status = SeatStatus.HELD;
        heldByBookingId = bookingId;
        holdExpiresAt = expiresAt;
        return true;
    }
  3. 3Represent the PNR as a Booking state machine

    A booking starts in draft, then moves to held after the seat lock succeeds, confirmed after payment/pricing succeeds, or cancelled/expired when the lifecycle ends.

    enum BookingStatus {
        DRAFT, HELD, CONFIRMED, CANCELLED, EXPIRED
    }
    
    interface BookingState {
        BookingStatus status();
        void confirm(Booking booking, long chargedAmountCents, Instant now);
    }
  4. 4Price by cabin class behind a strategy

    ReservationService asks a strategy for the final fare. The sample strategy uses a base economy fare, class multipliers, and a simple demand bump based on remaining seats.

    public interface PricingStrategy {
        Money price(Flight flight, Seat seat);
    }
  5. 5Use the service boundary as the transaction boundary

    ReservationService.holdSeat finds the flight and seat, creates the PNR, asks Booking to hold the seat, stores the booking, and emits an event. In production this method maps to a database transaction.

  6. 6Notify observers after durable lifecycle changes

    Observers are called only after a booking reaches held, confirmed, cancelled, or expired. Side effects are outside the core model, so a notification failure should not corrupt inventory.

Complete Java Implementation

Loading…

Explanation of Every Class

Flight

Owns the seat map for one scheduled flight. Its synchronized methods give the reservation flow a safe boundary for finding available seats, placing holds, confirming holds, releasing seats, and reporting remaining inventory by cabin class.

Seat

The smallest inventory unit. It knows its cabin class and state, stores the PNR that owns a hold, expires stale holds on access, and synchronizes hold/book/release so one seat cannot be sold twice.

Booking

The PNR and itinerary object. It delegates lifecycle behavior to BookingState implementations, ties passenger to flight and seat, records the charged fare, and exposes a concise itinerary summary.

PricingStrategy

The fare policy seam. ClassBasedPricingStrategy calculates cabin-class fares and a simple demand adjustment, while Money keeps fare values explicit in cents.

SearchService

The read-side service. It filters candidate flights by route, date, cabin class, and current availability without creating holds or changing booking state.

ReservationService

The application boundary for lifecycle operations. It creates PNRs via a factory method, stores bookings, confirms prices through the strategy, expires holds, and notifies observers after state changes.

Main

A small executable demo that builds a flight, searches business-class inventory, holds a selected seat, confirms the PNR, and prints observer notifications plus the charged fare.

Dry Run

Sample input

Flight AI101 DEL to BLR has seats 2A BUSINESS, 2B BUSINESS, and 10A ECONOMY. Hold duration is 10 minutes. Passenger Maya selects seat 2A and confirms before the hold expires.

StepActionSeat 2A stateBooking statusResult
1search DEL to BLR for BUSINESSAVAILABLEnoneAI101 returned because 2A and 2B are available
2holdSeat AI101 2A MayaHELD by PNR1001HELDPNR1001 created with expiry at now plus 10 minutes
3second passenger tries holdSeat AI101 2AHELD by PNR1001noneRejected because the seat is not available
4confirm PNR1001BOOKED by PNR1001CONFIRMEDPricing strategy charges business fare and observers are notified
5search BUSINESS againBOOKEDCONFIRMEDAI101 still has 2B, but 2A is no longer counted as available

The key moment is step 2: the seat and PNR become linked by the same id. Step 3 proves the lock works, and step 4 proves confirmation books only the seat held by that PNR.

Complexity Analysis

OperationTimeSpaceNote
search flightsO(F × S)O(R)F flights scanned, S seats checked per matching flight, R returned flights.
hold selected seatO(1)O(1)A seat lookup by seat number followed by a synchronized state change.
confirm bookingO(S)O(1)The sample pricing strategy reads remaining seats in the cabin. With cached counts this becomes O(1).
cancel bookingO(1)O(1)Map lookup, state transition, and seat release.
expire held bookingsO(B)O(1)B active bookings scanned by a scheduled sweeper.

The base design favors clarity over indexing. Production search usually indexes flights by route/date and keeps per-cabin available-seat counts so search and pricing do not scan every seat.

Extensibility

Multi-passenger PNR

Replace the single seat on Booking with a list of passenger-seat allocations, and make hold/confirm operate atomically across all selected seats.

Multi-leg itinerary

Introduce an Itinerary aggregate containing multiple bookings or flight segments. Confirmation should use a saga or transaction that either confirms every segment or releases all holds.

Advanced pricing

Add strategies for fare families, loyalty discounts, corporate fares, taxes, and promotional codes. ReservationService still asks only for price.

External notifications

Implement BookingObserver with email, SMS, and event-stream publishers. Keep failures retriable outside the inventory lock.

Durable inventory

Move flights, seats, and bookings behind repositories using database row locks or optimistic versions on seats to preserve hold correctness across app instances.

Alternative Designs

Class-level inventory hold

Instead of holding a concrete seat, hold one unit of BUSINESS or ECONOMY inventory and assign the exact seat during check-in.

Tradeoffs

Simplifies seat-map contention and supports airlines that assign later, but it does not satisfy experiences where customers pay for specific seats.

Repository-first domain model

Put FlightRepository, SeatRepository, and BookingRepository at the center and let services load and save aggregates per transaction.

Tradeoffs

More realistic for production and multiple app nodes, but it adds persistence noise to an interview focused on object boundaries.

Event-sourced booking lifecycle

Store events such as SeatHeld, BookingConfirmed, BookingCancelled, and HoldExpired, then rebuild current state from the event stream.

Tradeoffs

Excellent auditability for a regulated travel domain, but more complex than needed for a core LLD answer.

Common Mistakes

  • ×

    Treating a seat hold as a UI flag rather than a domain state protected by the seat object.

  • ×

    Confirming a booking without verifying that the same PNR still owns the held seat.

  • ×

    Representing booking lifecycle with multiple booleans such as paid, cancelled, and expired instead of a state machine.

  • ×

    Hard-coding business and economy prices inside ReservationService instead of using a pricing strategy.

  • ×

    Letting search mutate inventory or create PNRs, which couples the read path to the write path.

  • ×

    Forgetting hold expiry, causing seats to stay blocked after users abandon checkout.

  • ×

    Sending notifications before the state change succeeds, creating false confirmation messages.

Follow-up Interview Questions

QHow would you prevent overbooking across multiple application servers?

Persist each seat with a version or use a database row lock during hold and confirm. The invariant is still one active owner per seat, but the lock moves from the Java object to durable storage.

QHow do you support a passenger booking three seats at once?

Create an itinerary-level operation that attempts to hold all three seats in a deterministic order. If any hold fails, release the seats already held and fail the whole request.

QWhat happens if payment succeeds but confirmation fails?

Use an idempotent payment authorization followed by confirmation in a transaction or saga. If confirmation cannot book the seat, void or refund the authorization and notify the passenger.

QWhere should waitlisting fit?

Add a waitlist per flight and cabin class. When a booking cancels or expires, publish an inventory event and let a waitlist service offer the seat to the next eligible traveler.

QHow would dynamic pricing be added?

Introduce another PricingStrategy that uses demand, time-to-departure, fare buckets, and loyalty context. The reservation flow still depends only on the strategy interface.

Production Considerations

Database locking

Use row-level locks, compare-and-swap versions, or a strongly consistent inventory service for seat hold and confirm. In-memory synchronization is only correct within one process.

Hold expiry worker

Run a scheduled job that expires stale holds, releases seats, and emits events. The job must be idempotent because retries are normal.

Idempotency

Hold, confirm, cancel, and payment callbacks need idempotency keys so browser retries and webhook retries do not duplicate PNRs or charges.

Auditability

Record who held, confirmed, cancelled, or expired a seat and which fare was applied. Travel systems need a clear audit trail for disputes and support.

Operational metrics

Track hold success rate, expired holds, confirm latency, overbooking prevention failures, notification lag, and inventory mismatch counts.

What Interviewers Look For

  • Did the candidate make a concrete seat the unit of inventory and guard its state transitions?

  • Did they separate read-only search from mutating reservation operations?

  • Can they explain why a booking state machine is safer than lifecycle booleans?

  • Did they isolate pricing policy behind Strategy and event side effects behind Observer?

  • Can they discuss how the in-memory lock maps to database locks or optimistic versions in production?

Quiz

0/5 answered

  1. 1.Why should a seat hold store the owning PNR id?

  2. 2.Which pattern best models Draft, Held, Confirmed, Cancelled, and Expired booking behavior?

  3. 3.Why is pricing kept out of ReservationService?

  4. 4.What should happen when a held booking expires before confirmation?

  5. 5.Why notify observers after the booking state change succeeds?

Practice Variants

Add multi-passenger PNRs

Advanced

Let one PNR hold and confirm multiple seats for multiple passengers. Ensure partial failures release every seat already held.

Add fare buckets

Advanced

Model limited fare buckets within each cabin class and have the pricing strategy consume the lowest available bucket first.

Add waitlisting

Intermediate

When a cabin is full, let passengers join a waitlist and receive an observer event when cancellation or expiry releases a seat.

Flashcards

Cheat Sheet

Entities: Flight owns Seats; Seat owns inventory state; Booking is the PNR; ReservationService orchestrates; SearchService reads; PricingStrategy prices; BookingObserver emits side effects.

Patterns: State for booking lifecycle, Strategy for fares, Observer for booking events, Factory Method for PNR creation.

Flow: search route/date/cabin → select seat → hold seat with PNR and expiry → price selected seat → confirm same PNR → book seat → notify observers.

Invariants: no double booking, confirmation only for the PNR that owns the hold, expired holds release seats, cancelled bookings release seats when allowed.

Complexity: basic search O(F×S), selected-seat hold O(1), cancellation O(1), expiry sweep O(B).

Production: move locks to the database or inventory service, make operations idempotent, expire holds with a worker, and audit every fare and state change.

References

  • BookHead First Design Patterns (State, Strategy, Observer, Factory Method)Freeman & Robson
  • BookPatterns of Enterprise Application ArchitectureMartin Fowler
  • BookEffective Java — Item 34 (enums) and Item 78 (synchronize access to shared mutable data)Joshua Bloch
  • DocsRefactoring Guru — State Pattern