A self-transfer search only earns its place on the screen when it finds a combination cheaper than flying direct. To find one, every query fans out across many intermediate airports, and each extra hub multiplies the number of live price calls. Every one of those calls costs money and adds waiting time. The product exists only if a search can be wide enough to find the cheap combination and cheap enough to run on every query, including the many that never become a booking.
A flight price does not exist until someone asks for it.
It is natural to imagine flight prices sitting in a table somewhere, waiting to be read. They are not. An airline publishes fares, each with rules about when it can be sold, how far ahead, for which days and under which conditions. Separately, it controls how many seats it releases at each fare level, and it changes that allocation as the flight fills. The price a traveller sees is worked out at the moment of the question: find the cheapest fare whose rules are satisfied and whose seats are still open on that flight, then add taxes and charges. That calculation is what a live pricing call buys. It is slow because it is genuinely computed, and it is metered because most searches never turn into bookings, so providers charge for questions or ration them.
Airlines also price each market on its own terms. The fare between two cities reflects who else flies the route and what travellers will pay. Distance has little to do with it. A route served by a single carrier can cost more than two legs through a hub where several low-cost airlines compete. Many of those airlines sell only point to point and will not issue one ticket that connects onto another carrier. So a cheaper journey exists, and no airline will sell it as a single trip. A self-transfer product assembles it from two separate tickets. That gap between how markets are priced and how tickets are sold is the reason the product category exists.
Finding the cheaper journey is a combinatorial problem. For one origin and one destination, every plausible intermediate airport adds two more legs to price: origin to hub, and hub to destination. Flexible dates multiply that again. A search across even a modest set of hubs becomes a large number of live calls, and every extra hub adds cost and waiting time for every user, including people who are only browsing. Caches help, because a recent price is a good hint. A cached price is still a snapshot of a seat allocation that keeps moving. It can tell you where to look. It cannot tell you what the traveller will pay.
The brief read as flight search. In practice it was a spending problem. One origin and destination has to expand into a bounded set of candidate intermediate airports, price only the ones worth pricing, and answer inside the patience of someone holding a phone. Every paid call spent on a hub that was never going to win is budget taken from the hub that might.
Wide searches fail on cost long before they fail with an error.
The first way a fan-out search breaks is financial, and it is invisible in testing. A search that prices every hub live returns good results on a developer's machine and on a quiet afternoon. At real traffic the bill for live calls outgrows what bookings earn, because most searches are browsing and every one of them was charged in full. Nothing crashes. The product just stops making sense.
The second is latency in the tail. A fan-out cannot answer until its last call returns, so the slowest provider response sets the pace. The more calls a search makes, the more likely one of them is slow, which means the worst searches get worse faster than the average does. Fire all those calls at once and a provider's rate limit starts rejecting them, retries add more load, and the widest searches begin timing out at exactly the moment they were most likely to find something good.
The reflex, and the fix.
Query every provider for every hub
It is complete, and it is the most literal reading of the brief. Nothing can be missed if everything is asked.
Cost and latency both grow with the number of hubs. A search wide enough to be useful becomes too slow to wait for and too expensive to serve.
Generate, filter cheaply, then spend
Two stages in front of live pricing: a step that generates candidate hubs and a gate that checks them against cached prices. That is more machinery than calling the provider directly.
A wide search that stays inside a per-request cost budget, because the expensive call is only made for hubs that survived a cheap filter.
The expensive call goes last. The cheap filter removes most of the candidates, and the budget belongs to the search as a whole, so every hub has to earn its share of it.
Candidate hubs before any pricing
A route graph and the corridor rules produce the intermediate airports worth considering for this origin and destination. The fan-out starts from a reasoned shortlist, so an airport with no sensible routing between the two ends never costs a call.
A cached-price gate
Hubs on the shortlist are checked against a cached price feed first. The cached figure is a gate and never a quote. Its one job is to decide which hubs justify a paid call.
A ceiling on concurrent calls
The orchestrator holds a concurrency ceiling. A very wide search therefore gets slower. It does not trip a provider's rate limit, and it does not run up a bill nobody budgeted for.
Pricing the nonstop in the same pass
A self-transfer result that does not beat the nonstop is not a result. The nonstop baseline is priced in the same search, so the comparison is like for like, two prices taken at the same moment.
What each choice cost, and what it bought.
AGenerating candidates before pricing anything
The cheapest live call is the one never made, and the surest way not to make it is to never consider the hub. A route graph and corridor rules turn an open question into a shortlist before any money is spent. The cost is a route graph to maintain, and a hub left off the list can never be found, even on the day it would have won. What it buys is control: the width of the fan-out becomes a design decision. Searching everything pays for impossible hubs on every query, forever.
BUsing the cache as a gate and never as a quote
Cached prices were given exactly one job, deciding which hubs deserve a paid call. That has a cost. A hub whose cached price is stale on the high side can be filtered out when a fresh price would have won, and a hub that passes can re-price upward on the live call. What it buys is that staleness can never mislead a traveller, because no number on the results page ever comes from the cache. Showing cached prices directly would have been faster and cheaper, and it would have put out-of-date figures in front of people at the exact moment they decide.
CAccepting slower wide searches through a concurrency ceiling
Capping how many calls run at once means the widest searches take longer than they would with everything fired together. In return the system degrades gradually and predictably. A wide search gets slower. It does not trip a provider's limit, start a retry spiral, or produce a bill nobody planned for. Unbounded parallelism looks faster in a demo and falls off a cliff in production, and a cliff is the one shape a consumer product cannot recover from mid-search.
DPricing the nonstop in the same pass
Every search pays for one more live call, the direct flight, on top of the hubs. That call is what keeps the product honest. A self-transfer result is only shown as a saving when it beats a nonstop priced in the same search, at the same moment. Comparing against a typical or cached nonstop price would make savings look larger on the page and then disappear at checkout, which does more damage to trust than showing no saving at all.
A single headline number hides where a system fails. This work was scored on the dimensions that actually decide whether it holds in production, measured on real, held-out cases rather than the demo path.
Latency, timeout and cache-hit figures are measured on the delivered system.
A search that asks every provider about every hub, returns correct results eventually, and costs more per search than the booking earns.
A bounded fan-out that generates candidates, filters them against cached prices and spends live calls only on the survivors. It returns in 4.5 seconds on average and 8.7 seconds at the ninety-fifth percentile, with a 1.3 percent timeout rate and a 35 percent cache hit rate on the pre-filter.
What it owns, and what it hands to a person.
Results are only as fresh as the cached pre-filter. A hub that passes the gate can still come back more expensive on the live call. The live number is authoritative and the cached one only decides what to ask about. The product is built to treat them that way and to show the live price, even when it is the less flattering of the two.
Any fan-out across paid providers has this shape. Expand into candidates, filter with something cheap, spend the expensive call only on what survived, and hold the budget per request so a wide request cannot quietly cost more than it earns.
A flight price is calculated when someone asks for it, from fare rules and the seats still open, which is why live pricing is slow, metered and rationed.
Self-transfer journeys exist because airlines price each market separately and many will not sell a connection onto another carrier. The saving is genuine and so is the cost of finding it.
In any fan-out, cost and waiting time grow with width. Set the budget per request and make each candidate earn its share before it is called.
A cached price is a good hint about where to look and a poor promise about what someone will pay. Use it to filter and never to quote.
Cap concurrency so that a wide request gets slower gradually. An uncapped fan-out is fast until it hits a provider's limit, and then it fails all at once.