← Back
Home › Systems › Consumer applications

flight-search

Answers a route-and-dates query with ranked itineraries and prices, searching schedules, fares and rules directly or by fanning out to suppliers.

ITA, Skyscanner

Open the flight-search pageflight-search Consistent hashingdistributed: Hash nodes and keys onto a ring; each key belongs to the next node clockwise, so membership changes move only about 1/n of keys.›Currency cross ratetriangulationfinance: Deriving cross rates through a base currency with correct precision, inversion and spreadsMoney representationfinance: Store money as integer minor units with an explicit currency exponent, compute in wider integers, and round once with a named rounding mode.›K shortest pathsgraphs: Yen's / Eppstein's k shortest loopless paths for alternative itinerariesRaptor transit routinggraphs: Round-based public transit / schedule routing (RAPTOR) with Pareto setsGradient-boosted decision trees(GBDT)machine-learning: Add small regression trees one at a time, each fitted to the loss gradient; histogram splits over 256 bins make it the strongest tabular default.›Learning to rankmachine-learning: Train a scorer on query-grouped data so that the order it induces maximizes NDCG; LambdaMART (boosted trees with NDCG-weighted pair gradients) is the production default.›Top-k selectionsearch-sort: Keep the k best of n items without sorting all n - bounded min-heap for streams, quickselect for arrays, threshold pruning to skip most items.›Calendar date arithmeticsystems: Civil dates, time zones (tzdb), DST gaps, month-end anchoring and duration mathCircuit breakersystems: Stop calling a failing dependency for a while, probe it with trial calls, and resume on recovery: fail fast instead of piling up timeouts.›Hedged requestssystems: Sending a backup request after a latency percentile and taking the first answer to cut tail latencyRete rules enginesystems: Match many rules against changing facts by caching partial matches in a shared network, so each change only does the work it actually affects.›Stale while revalidatesystems: Serving cached data past TTL while refreshing in the background, with request coalescingToken bucketsystems: A bucket of up to b tokens refilled at rate r; requests spend tokens, enforcing an average rate while allowing bursts of b.›W-TinyLFUsystems: An LRU window plus segmented LRU, guarded by a 4-bit Count-Min sketch that admits a newcomer only if it beats the victim's frequency.›Largest remainder roundingfinance: Splitting an amount into parts that sum exactly (largest remainder / Hamilton) for allocations, splits and prorationsDijkstra's algorithmgraphs: Single-source shortest paths with non-negative edge weights by settling vertices in order of distance with a priority queue.›BFS and DFSgraphs: The two basic graph traversals; BFS gives hop distances and levels, DFS gives discovery/finish order for cycles, components and topological order.›d-ary heapdata-structures: Array heap with d children per node (d = 4 typical): half the binary heap's depth, siblings in one cache line, faster priority queues.›Quantile sketchesdata-structures: Mergeable quantile sketches: t-digest, DDSketch, HDR histogramsRegularizationmachine-learning: Techniques that trade training fit for generalization: weight decay, dropout, early stopping, label smoothing and data mixing, with the exact update rules and defaults.›Loss functionsmachine-learning: The scalar a model minimizes: cross-entropy for classes and tokens, MSE or Huber for values, each with a stable form and simple gradient.›Evaluation metricsmachine-learning: Precision, recall, ROC AUC, average precision, log loss, NDCG and pass@k computed correctly, plus paired confidence intervals for comparing models.›Sliding-window counterssystems: Count events in the last W seconds with a ring of sub-bucket counters; the two-bucket weighted estimate gives rate limits in 16 bytes per key.›Swiss tabledata-structures: Open-addressing hash table that filters 8-16 slots at once with 1-byte hash tags and SIMD, giving fast lookups at 87.5 % load.›Hash joinstorage: Build a hash table on the smaller input's join key and probe it with every row of the larger one; the linear-time workhorse for equi-joins.›Arena allocatorsystems: Allocate by bumping a pointer through big chunks and free everything at once: a few instructions per allocation for objects sharing a lifetime.›Sparse matrices (CSR)numerics: Compressed sparse row stores only nonzeros as row pointers, column indices and values; SpMV streams them once, so it is memory-bandwidth bound.›Bootstrap confidence intervalsstatistics: Resampling-based confidence intervals for arbitrary statistics

How it is built

Architecture overview

A flight-search system answers "SFO to BOS on April 2, back April 5" with a ranked list of itineraries, each a set of flights and a price. It is a shortest-path problem only on the surface. The flights form a time-dependent network, but the price is not the sum of per-flight prices: it comes from fares with rules (advance purchase, minimum stay, allowed combinations) that apply to groups of flights, and whether a fare may be used depends on the other flights and fares in the same ticket. Carl de Marcken of ITA Software, whose engine powered travel sites and airline sites, described this in his MIT 6.034 course notes (2003) and presented complexity results showing why the cheapest-fare question is hard.

Two architectures are in use, and many systems mix them. Own-data search loads schedules, fares and rules and searches them in-house; this is the ITA design described in the paper. Metasearch fans a query out to many suppliers (airlines, consolidators, agents) and merges what comes back, trading control for freshness. Both need the same core: build candidate itineraries, price them, rank them, and keep latency low with caches whose entries are known to go stale. The one idea that makes it work: separate the cheap, cacheable part (which flights connect) from the expensive, volatile part (which fares apply and whether seats are available), and spend your effort in the second only for itineraries that survive the first.

Parts:

 query (O, D, dates, pax, filters)
        │
        ▼
   normalise ──► cache (key: O-D-date-pax-cabin ...) ──hit, fresh──► rank ──► response
        │ miss / stale                    ▲
        ▼                                 │ store with TTL, flag stale
   connection builder ──► candidate itineraries (flights only)
        │                       │
        │               prune: min connect time, max duration, routing sanity
        ▼
   fare + rules engine  ◄── fare/rule data   +   availability / live price from suppliers
        │                                         (quota, timeout, circuit breaker, hedge)
        ▼
   priced itineraries ──► currency, taxes ──► rank (score, top-k, diversity) ──► response
        │
        └── later: price check at selection ──► booking

How the parts fit together

Inventory and fare data

de Marcken's notes list three kinds of data the engine runs on, provided electronically by the world's airlines: flight schedules, prices, and seat availability, plus a large share of published data used by all the major engines and some private data restricted to individual parties. As of 2003 he gives their update rates: flight data daily with occasional faster updates for unexpected cancellations, prices about ten times a day, seat availability continuously. The numbers have changed since, but the structure matters: the three kinds change at different speeds, so they are cached and refreshed at different speeds. A schedule is stable enough for hours; a fare for minutes to hours; availability can change between two clicks.

The data contract between the feed and the engine should be an immutable snapshot version per data kind. An engine that mixes yesterday's schedule with this morning's fares must know it, because a fare may refer to flights that no longer exist. Event deduplication keeps repeated feed messages from creating duplicate entries.

Amounts are integer minor units with a currency; dates are calendar dates with time zones: a flight departs on one date and arrives on another, and a fare rule counted in days is evaluated on local dates (calendar-date-arithmetic, no page yet).

Connection builder

For one origin and destination the builder lists sequences of flights that a human would accept. In graph terms: nodes are airport-times, edges are flights, and waiting is allowed if it respects a minimum connection time at that airport. This resembles public-transit journey planning, where RAPTOR (Delling, Pajor, Werneck) computes all Pareto-optimal journeys for arrival time and number of transfers by rounds: round k holds the best arrivals using k trips, so the number of transfers is built into the search rather than added to the cost. The paper's abstract says the algorithm is not Dijkstra-based and looks at each route at most once per round (raptor-transit-routing, no page yet). For air travel the same round structure gives itineraries by number of connections.

Why not plain Dijkstra? Because the aim is not one shortest path but a set of distinct, reasonable ones, and the "cost" is not additive. De Marcken's notes show that the number of flight combinations grows exponentially with trip length, that the number of round-trip combinations is roughly the square of one-way ones, and that Dijkstra-style tricks cannot reduce the exponential number of paths to polynomial. He adds that standard algorithms can produce the k shortest flight paths but that enumerating good paths fast enough for a planner is a considerable challenge. The practical answer is a bounded enumeration: generate the best few thousand per route by duration and connection quality (k-shortest-paths, no page yet; Yen's 1971 algorithm for k loopless shortest paths is the classic reference), prune by minimum connection time and maximum duration, and check time-window constraints incrementally (Time-window feasibility gives the O(1) check for an insertion). Everything before pricing deals only with flights, so its result can be cached per route and date.

Fare and rules engine

This is the core difficulty and the reason the problem is hard. From de Marcken's notes: a fare has rules that restrict its use; a fare component is a fare plus the flights it covers; a priceable unit groups one to four fare components; many fare rules apply to the priceable unit as a whole (a minimum stay, a Saturday-night stay, a restriction that a round-trip fare may not be used one-way). A traveller may choose any combination of fares as long as every rule holds, usually the cheapest. The same flights can be grouped into fare components and priceable units in several ways, and that choice changes how rules are read. The notes show rules that test combinations of two flights far apart in time and on the ticket, and he presents complexity results for the full problem, including that for some rule combinations the general search is undecidable.

A Rete rules engine fits the rule side: thousands of fares each with conditions over the same facts (dates, days of week, booking date, flight numbers, cabin, passenger type), and many candidate itineraries that share most facts. Rete caches the partial matches so evaluating a new itinerary re-does only what differs. It does not solve the combination search itself; that remains an optimisation over fare groupings, and the rules engine is used to validate each candidate grouping. It is also a real system: expect fare data in vendor formats, with a long tail of special cases, which the engine must treat as data, not code.

Contract with its neighbours: input is a candidate itinerary (flights with local dates and times) plus a pricing date; output is zero or more priced options (fare components, base amounts, rule notes, the fare basis to buy) and, for each, the availability it needs.

Pricing and currency

Totals combine base fares, taxes and fees, each in its own currency. Money representation says: integer minor units, one named rounding mode, round once. Showing the price in the traveller's currency needs a rate and a convention for whose rate and when; currency-cross-rate-triangulation (no page yet) is the calculation when two currencies have no direct quote, and FX conversion and quoting covers the system view. A price shown in one currency and charged in another must say which rate applied, or the displayed and charged amounts will disagree.

Ranking

A search can yield thousands of valid priced itineraries; the page shows tens. The score blends price, duration, number of stops, departure time, airline and the risk of a short connection. A plain weighted sum with top-k selection (bounded heap, so you never sort everything) is the baseline. When there is click and booking data, learning to rank with gradient-boosted trees (LambdaMART in Burges's overview) learns what people choose; features are price relative to the cheapest option, extra duration, connection quality, and time of day. Pure price ranking produces many near-identical results from one airline; add diversity: guarantee at least one nonstop, one cheapest, one fastest. This is design reasoning rather than a documented recipe for any particular product.

Cache and freshness

Searches repeat: many people ask the same route and dates. Caching the result for a key (origin, destination, date, passengers, cabin) answers most requests without touching suppliers. Three techniques fit together:

  • Admission and eviction with W-TinyLFU: a window plus a frequency sketch admits an item only if it beats the victim, which suits skewed query popularity and keeps one-off searches from pushing out popular routes.
  • Staleness: price entries get a short time to live; after it, stale-while-revalidate (no page yet; the HTTP form is RFC 5861) serves the old value once while refreshing in the background. Which fields may be stale differs: a schedule can be hours old, a price minutes old, availability should be treated as a hint. See Cache invalidation and Tiered cache.
  • Placement: consistent hashing on the key routes a query to the node that holds its entry, and keeps a node join or loss from emptying the whole cache.

Cached prices are indications. The booking step must re-price (see below).

Supplier gateway

Metasearch fans out to many suppliers and must tolerate slow and failing ones; this is the usual tail-latency problem. A hedged request sends a duplicate to a second replica or route after the typical latency has passed and uses whichever answers first (Dean and Barroso, The Tail at Scale), hedged-requests, no page yet. A circuit breaker stops calling a supplier that fails repeatedly. A token bucket keeps you inside each supplier's quota. Exponential backoff with jitter spaces retries. The response to the user is built at a deadline: whatever has arrived by then, with the slow suppliers' results streamed later or dropped.

Booking hand-off

When the traveller picks an itinerary, the system re-prices it with the supplier and checks availability; only then does the booking system hold and ticket it, with idempotency keys so a retry does not double-book. The difference between the cached and the live price is the most common user-visible failure of this whole stack.

Data and control flow

One search:

  1. The request is normalised (airport codes expanded to city groups, dates validated against time zones) and a cache key is built.
  2. The cache is consulted. A fresh hit goes straight to ranking. A stale hit is served and a refresh queued. A miss continues.
  3. The connection builder produces candidate flight sequences from the schedule snapshot. This is cacheable per route and date and is cheap relative to pricing.
  4. The fare and rules engine prices the candidates in order of promise until a budget of candidates or time is spent; for metasearch, the supplier gateway runs in parallel with a deadline.
  5. Currency and tax are applied; results are scored; the top k are chosen with diversity; the response and the cache entry are written.
  6. Time goes mostly to pricing (rule evaluation and supplier calls), not to graph search.

One refresh: a background job re-runs the search for popular keys whose entries are about to expire, weighted by how often they are asked; it uses the same engine at lower priority and respects supplier quotas.

One booking: re-price with the selected itinerary and the live supplier; if the amount changed, show it and ask; if availability is gone, return alternatives.

Scaling

  1. Pricing cost: the number of candidates times the fare combinations. Levers: prune candidates earlier (connection quality, duration), cache priced itineraries per route and date, limit combinations per query and rank the promising first.
  2. Cache hit rate: the cheapest way to scale; route popularity is skewed. Levers: W-TinyLFU admission, longer TTLs for schedule-only results, background refresh for the head of the distribution.
  3. Supplier latency and quota in a metasearch: hedging, deadlines, per-supplier budgets, circuit breakers, and priority between interactive and refresh traffic.
  4. Fan-out for flexible dates: "cheapest in the next month" is thirty or more searches. Lever: precomputed per-route calendars from cached results, clearly marked as indications.
  5. Data refresh: fares change many times a day (de Marcken, 2003 figure); loading must not stall searching. Lever: build the new snapshot beside the old one and switch atomically.

Failure modes

FailureSymptomCauseFix
Price changed at checkoutthe price rises after the user selectscache returned a stale indication; fare sold outre-price at selection, show cache age, shorter TTL on volatile routes
Rule misapplieda fare shown that cannot actually be ticketedrule interpretation error, wrong priceable-unit grouping, stale rule datatest against supplier answers; keep rule data versioned with the snapshot
Impossible connectionitinerary with too short a transferminimum connection time missing or wrong for that airport or terminal changedata check, conservative default, flag risky connections
Date errorsarrival "before" departure, or off-by-one day on a fare ruletime zones and local dates mixedstore local time plus zone, convert once, use calendar arithmetic that knows zones
Slow tailsome searches take many secondsone slow supplier holds the responsedeadlines, hedged requests, return partial results
Retry stormsuppliers throttle or banretries without backoff, no quota controltoken bucket, backoff with jitter, circuit breaker
Rounding mismatchtotal differs by a cent between screensrounding at each step or mixed rounding modesinteger minor units, round once
Cache stampedemany identical misses at once on a hot keyexpiry of a popular entrystale-while-revalidate, request coalescing

Real implementations

namewhat it isrepolicencenotes
ITA Software search engineown-data flight search; powered travel sites, airlines and reservation systems——documented in de Marcken's course notes (2003); not open
OpenTripPlanneropen-source multi-modal trip planner for scheduled public transport (GTFS)opentripplanner/OpenTripPlannerLGPL-3.0-or-laterclosest open relative of the connection builder; the repository contains a raptor module; not a flight engine
IATA NDCairline distribution standard for offers and orders——see the IATA programme page; defines how retailers get offers from airlines

No open-source flight-shopping engine with fare-rule evaluation was found for this page; public documentation of production flight search is mostly the ITA paper and academic work on related routing problems.

Sources

  • C. de Marcken, Computational Complexity of Air Travel Planning, annotated slides, ITA Software, notes for MIT 6.034, 2003. PDF (mirror)
  • D. Delling, T. Pajor, R. F. Werneck, Round-Based Public Transit Routing (RAPTOR). ALENEX paper and publication page
  • J. Y. Yen, Finding the K Shortest Loopless Paths in a Network, Management Science 1971, DOI 10.1287/mnsc.17.11.712 (confirmed via Crossref).
  • C. L. Forgy, Rete: A fast algorithm for the many pattern/many object pattern match problem, Artificial Intelligence 1982, DOI 10.1016/0004-3702(82)90020-0 (confirmed via Crossref).
  • J. Dean and L. A. Barroso, The Tail at Scale, Communications of the ACM 2013. Google Research page
  • G. Einziger, R. Friedman, B. Manes, TinyLFU: A Highly Efficient Cache Admission Policy. arXiv:1512.00727
  • C. J. C. Burges, From RankNet to LambdaRank to LambdaMART: An Overview, Microsoft Research. publication page
  • M. Nottingham, HTTP Cache-Control Extensions for Stale Content, RFC 5861. RFC
  • IATA, NDC programme page.
  • OpenTripPlanner: documentation and repository.

Similar systems

Share the most building blocks with this one.