Low Level Design: Airline Reservation System

Airline reservation systems are among the most demanding transactional workloads: thousands of concurrent seat holds, strict inventory partitioning by fare class, a multi-step booking state machine, and hard deadlines like check-in cutoff. This low level design covers the core schemas and flows.

Flight Schema

CREATE TABLE flight (
  flight_id          BIGINT PRIMARY KEY AUTO_INCREMENT,
  flight_number      VARCHAR(10) NOT NULL,
  origin             CHAR(3) NOT NULL,
  destination        CHAR(3) NOT NULL,
  scheduled_departure DATETIME NOT NULL,
  scheduled_arrival   DATETIME NOT NULL,
  aircraft_type      VARCHAR(50) NOT NULL,
  status             ENUM('scheduled','boarding','departed','arrived','cancelled') NOT NULL DEFAULT 'scheduled',
  INDEX (origin, destination, scheduled_departure)
);

Seat Schema

CREATE TABLE seat (
  flight_id     BIGINT NOT NULL REFERENCES flight(flight_id),
  seat_number   VARCHAR(4) NOT NULL,
  class         ENUM('economy','business','first') NOT NULL,
  status        ENUM('available','held','booked') NOT NULL DEFAULT 'available',
  held_until    DATETIME,
  booking_id    BIGINT,
  price         DECIMAL(10,2) NOT NULL,
  PRIMARY KEY (flight_id, seat_number),
  INDEX (flight_id, class, status)
);

held_until enables TTL-based expiry of abandoned holds without a background sweeper — the application checks it on any read of a held seat.

Booking State Machine

  1. Search: passenger queries origin, destination, date, class, and number of seats.
  2. Select: passenger chooses a flight and specific seat(s).
  3. Hold: seats are atomically set to held with held_until = NOW() + interval 15 minute using a conditional UPDATE that only succeeds if current status is available. A booking record is created in pending state.
  4. Payment: passenger submits payment details. Payment processor is called. On success, proceed to confirm.
  5. Confirm: seat status updated to booked, booking status to confirmed, PNR generated and returned to passenger.
  6. Expiry path: if payment is not completed before held_until, the hold lapses. On next availability check the seat reverts to available.

PNR Generation

A Passenger Name Record (PNR) is the unique locator for a booking. It is a 6-character alphanumeric code (uppercase letters and digits, excluding ambiguous characters O, 0, I, 1) generated at confirmation time. Generation: take a base-34 encoded value of a sequence ID, left-pad to 6 characters, and verify uniqueness in the booking table. Collision probability is negligible given the ~1.5 billion possible values, but a uniqueness check with retry handles it.

CREATE TABLE booking (
  booking_id  BIGINT PRIMARY KEY AUTO_INCREMENT,
  pnr         CHAR(6) UNIQUE,
  passenger_id BIGINT NOT NULL,
  flight_id   BIGINT NOT NULL REFERENCES flight(flight_id),
  seat_number VARCHAR(4),
  fare_class  CHAR(1) NOT NULL,
  total_price DECIMAL(10,2) NOT NULL,
  status      ENUM('pending','confirmed','cancelled','checked_in','no_show') NOT NULL DEFAULT 'pending',
  created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX (flight_id, status)
);

Fare Class Pricing

Fare classes partition economy inventory into buckets with different prices and restrictions. Common classes:

  • Y: full-fare economy — fully refundable, no change fee, highest price.
  • Q: discount economy — non-refundable, change fee applies, lower price.
  • J: business class — separate cabin, highest fare, priority boarding.
CREATE TABLE fare_inventory (
  flight_id   BIGINT NOT NULL REFERENCES flight(flight_id),
  fare_class  CHAR(1) NOT NULL,
  total_seats INT NOT NULL,
  sold_seats  INT NOT NULL DEFAULT 0,
  price       DECIMAL(10,2) NOT NULL,
  PRIMARY KEY (flight_id, fare_class)
);

When a seat is booked in fare class Q, sold_seats is incremented for that class. If sold_seats = total_seats for Q, no more Q-class bookings are accepted even if Y-class seats remain — each bucket is independent.

Seat Hold with Distributed Lock

For multi-seat bookings (e.g., a family of four), all seats must be held atomically. The approach: acquire a Redis lock keyed on flight_id with a short TTL (2 seconds), execute the batch seat UPDATE, release the lock. If the lock cannot be acquired within 500ms, return a retry-later response. This avoids partial holds where some seats succeed and others fail.

Waitlist

When a flight is sold out in a given fare class, passengers can join a waitlist. The waitlist is ordered first by fare class (J before Y before Q) then by join time. On a cancellation, the freed seat is offered to the top waitlist entry matching the same or lower cabin class. The system sends a notification with a 30-minute response window; if not accepted, the next entry is tried.

Baggage Allowance

CREATE TABLE baggage_allowance (
  fare_class     CHAR(1) NOT NULL,
  carry_on_kg    TINYINT NOT NULL,
  checked_bags   TINYINT NOT NULL,
  checked_bag_kg TINYINT NOT NULL,
  PRIMARY KEY (fare_class)
);

Extra bags beyond the allowance trigger a fee recorded as a separate line item on the booking. Excess baggage at the gate is flagged during check-in.

Check-in Window Enforcement

Online check-in opens 24 hours before departure and closes 45 minutes before. The check-in endpoint validates: NOW() >= departure - interval 24 hour and NOW() <= departure - interval 45 minute. Bookings not checked in by cutoff are marked no_show and the seat is released for standby passengers at the gate.

Key Design Decisions

  • 15-minute seat hold with TTL prevents inventory being locked by abandoned sessions without requiring a scheduled cleanup job.
  • Fare class inventory buckets are a separate table so pricing and availability per class can be queried without scanning all seat rows.
  • Distributed lock on multi-seat holds prevents partial allocation across concurrent requests for the same flight.
  • Waitlist priority by fare class ensures higher-revenue passengers are accommodated first on cancellations.

Frequently Asked Questions: Airline Reservation System

What is an airline reservation system in low level design?

An airline reservation system LLD problem asks you to design the classes, data model, and workflows for searching flights, selecting seats, holding inventory, processing payment, and issuing tickets. Core entities: Airport, Aircraft, Flight (flight number, route, schedule, aircraft), FlightInventory (seat counts per fare class), Seat (seat number, class, features), Passenger (profile, frequent flyer number), Booking (PNR, passengers, segments, fare, status), Ticket (ticket number, e-ticket record), and Payment. The system must handle high concurrency (many users simultaneously booking the last seats on a popular flight), seat hold-and-release mechanics during the payment window, fare class inventory management, itinerary changes, and cancellation/refund rules. It is distinct from hotel booking in its use of fare class buckets, PNR generation, and multi-segment itinerary support.

How does the seat hold mechanism work during booking?

When a user selects seats and proceeds to payment, the system must prevent those seats from being sold to someone else during the typically 10–15 minute payment window. Seat hold steps: (1) The user selects a flight and seat(s); the system attempts to place a hold — decrement the available count for the relevant fare class bucket in the FlightInventory table using an atomic compare-and-swap (UPDATE … SET available = available – N WHERE available >= N). If the update affects 0 rows, the seats are no longer available — return an error. (2) Create a SeatHold record with hold_id, flight_id, seat_ids, passenger_count, fare_class, hold_expiry (now + 10 minutes), and status = active. (3) A background job (or TTL-based mechanism) scans for expired holds every minute and releases them — incrementing the fare class available count back and marking the hold as expired. (4) On successful payment, transition the hold to confirmed, create the Booking and Ticket records. On payment failure or user abandonment, the hold expires and inventory is returned. This prevents indefinite inventory lockout while still protecting the user’s selection during checkout.

What is a PNR and how is it generated?

PNR stands for Passenger Name Record — a unique alphanumeric identifier (typically 6 characters, e.g., “ABC123”) that serves as the master reference for a booking itinerary in the Global Distribution System (GDS) and the airline’s departure control system. It groups all passengers, flight segments, fare details, and contact information for a single trip. Generation: the PNR code must be unique across the airline’s system (and across GDS networks in the case of interline bookings). Common generation approaches: (1) Random base-26/base-36 string from a CSPRNG, checked against the Booking table for collision (extremely rare for 6 characters: 26^6 = ~308 million combinations). (2) Encoded sequential ID — encode a database sequence number in base-36 to produce a short alphanumeric string (guarantees uniqueness, no collision check needed). The PNR is printed on boarding passes and itinerary emails and is used as the retrieval key for “manage my booking” flows. It is distinct from the ticket number (which is assigned per passenger per segment after ticketing and follows IATA format: 3-digit airline code + 10-digit number).

How do fare class inventory buckets control pricing in an airline system?

Airlines divide each flight’s cabin into fare class buckets (also called booking classes) — typically labeled with letters (Y, B, M, K, … for economy; J, C, D, … for business). Each bucket has a distinct price point, refundability rules, change fees, and minimum stay requirements. The FlightInventory table stores available_seats per (flight_id, fare_class). Revenue management software continuously adjusts how many seats are open in each bucket based on demand: when a flight is selling well, the airline closes cheaper buckets (sets available = 0) and forces new bookings into higher-priced ones. When demand is low, it opens cheaper buckets to stimulate sales. At booking time: the user’s selected fare maps to a specific fare class code; the system checks available > 0 for that class and decrements it on hold. This is why the same flight shows different prices depending on how many seats remain in each bucket and when you search. In the LLD, model this as a FareClass entity linked to FlightInventory, with price and rules stored in a FareRule table that the PricingEngine consults when quoting fares.

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering

Scroll to Top