Development

Ordering a Fallback Ladder by Cost and Latency

Antonio Evans9 min read

A side view of a tall ladder leaning against a wall labelled with a clock and a dollar sign.
Key points

Key points

  1. 1Order your fallback ladder by the cost of the next second of waiting, not by the raw price list or the raw speed of each model.
  2. 2Give every request a latency budget, and treat the time already spent on a slow primary call as money already spent.
  3. 3Compute expected cost per candidate as price per call plus a dollar penalty for the extra latency it adds, then sort ascending.
  4. 4When the primary misses its budget, walk the ladder from the lowest penalty upward, and degrade the answer only when the cap forces it.

Start with a latency budget, not a price list

Most fallback chains are written in the order a team happens to have configured API keys. The primary is the good model, the secondary is the cheaper one, the tertiary is the one someone remembered to add. That ordering is a price list, not an engineering decision. It says nothing about what happens when the primary call is slow rather than failing.

The right starting point is a per-request latency budget. Every user-facing request gets a number: the wall-clock time after which the answer is no longer useful, whether the request succeeded or not. For a chat reply that streams, that may be time to first token. For an agent that chains tool calls, it is the sum of the whole chain, because latency compounds at every step and the user waits on the total. For a background enrichment job, the budget may be generous enough that latency barely matters and price dominates.

The budget does two jobs. First, it defines the trigger: when the primary has consumed its share of the budget without producing a usable answer, the fallback fires. Second, it defines the price of waiting. If you have decided a request has 3,000 ms, then every millisecond past that line has a real cost, in abandonment, retries and support load. You can put a dollar figure on it once you know your conversion or retention curve. Even a rough figure is enough to make the ordering decision correctly.

This is the same discipline as setting timeouts on edge functions. A platform will enforce its own limits whether or not you planned for them: Supabase documents a 150 second wall clock limit on the free plan and a 504 when the function does not respond in time, and Netlify sets a 40 second response header timeout. Your fallback logic needs its own budget smaller than those ceilings, otherwise the platform kills the request before the ladder reaches the second rung.

Put a number on the penalty for waiting

The core insight is that a slow primary and a slow fallback are not the same kind of problem. If the primary is slow, you have already paid part of the latency. The fallback starts a new clock, and its full latency is added on top of what the user has already waited. That is why a fast-looking fallback can be the more expensive choice.

So each candidate model needs three numbers, measured on your own traffic rather than read off a vendor page:

  1. Price per call for your typical input and output shape, in dollars.
  2. Median and 95th percentile latency for that same shape, in seconds. Use the high percentile, because fallbacks are triggered by slow conditions.
  3. A waiting penalty in dollars per second, derived from your latency budget. If a second of overrun costs you $0.002 in lost completion or support, that is the coefficient.

The expected cost per candidate is:

expected_cost = price_per_call + waiting_penalty * added_latency_seconds

where added_latency_seconds is the candidate's latency, not the primary's. You are comparing what it costs to serve the request from this candidate right now.

A worked example with three models shows why raw ordering fails. Assume a waiting penalty of $0.002 per second, and a primary that has already spent 4 seconds and missed its 3 second share of the budget.

CandidatePrice per callp95 latencyPenalty ($0.002/s)Expected cost
Model A (frontier)$0.01206.0 s$0.0120$0.0240
Model B (mid)$0.00602.5 s$0.0050$0.0110
Model C (small)$0.00151.0 s$0.0020$0.0035

Sorted by expected cost, the ladder is C, B, A. Sorted by price, it is C, B, A too, but only because latency happens to fall in the same direction. Change the numbers and the answers diverge. Suppose Model C is a tiny model with a cold start problem on some infrastructure, pushing its p95 to 5 seconds. Its expected cost becomes $0.0015 + $0.0100 = $0.0115, which now sits between B and A. A cheaper model with a long tail belongs lower on the ladder than its price suggests.

The same logic explains why serverless cold starts matter here. A function that has not been called recently pays a startup cost that Lambda cold starts can put anywhere from half a second to a few seconds, and that cost lands squarely in your waiting penalty. If your fallback lives on a cold edge function, its measured p95 will reflect the cold path, and it will rank accordingly. Keeping the fallback warm (provisioned concurrency, periodic warm-up requests) can move it up the ladder without changing its price at all.

Work out the break-even point before you sort

The penalty coefficient is the lever that decides everything. If you plot expected cost against the penalty rate for two candidates, they cross at a break-even point. Below that point, price dominates. Above it, latency dominates.

Take Model A at $0.0120 and 6.0 seconds against Model B at $0.0060 and 2.5 seconds. The price gap is $0.0060 and the latency gap is 3.5 seconds, so they break even at a penalty of $0.0060 / 3.5 = $0.0017 per second. If your users tolerate the wait well enough that a second is worth less than 0.17 cents, put A first on price grounds. If a second is worth more than that, B wins and A falls behind it.

This break-even number is the decision rule. Write it next to the ladder. It tells the next engineer why the order is what it is, and it tells you when to revisit the order: whenever the penalty coefficient moves, or whenever measured p95 latency shifts. It also handles a subtle case. A provider can be technically up while returning time to first token of several seconds instead of the usual fraction of a second, a failure mode that fallback routing addresses with explicit latency thresholds rather than error rates. Your ladder is ordered by the same quantity those thresholds measure.

Two practical rules fall out of the arithmetic.

Do not retry the same model before moving down the ladder. A retry re-pays the primary's latency in full and leaves you with the same expected result. Bounded retries are useful for transient network errors, but for a slow call the correct next action is the next rung, not the same one again.

Never let a fallback's latency exceed the budget that remains. If 1 second is left and the next candidate's p95 is 2.5 seconds, it cannot serve this request at all. Skip it. This is why the ladder must be sorted by latency as well as cost: a candidate that cannot finish within the remaining budget has infinite expected cost, whatever its price.

When to degrade the answer instead of escalating

A ladder that only ever escalates to better models is a ladder that always gets slower under load. Sometimes the correct move is downward: return a shorter answer, skip an optional tool call, or serve a cached result. Deciding when to do that is a policy question, and the latency budget already contains the answer.

The rule: escalate only while the next rung's expected latency fits inside the remaining budget. When no rung fits, degrade. Degradation options, roughly in order of user impact:

  • Return the streamed partial answer with a note that it was truncated.
  • Serve a cached or precomputed answer for the same input, accepting that it may be stale.
  • Fall back to a deterministic, non-model path (template, search result, rules engine).
  • Return an honest error with a retry affordance.

Caching deserves a caveat here. A cache is not a fallback rung in the same sense, because its hit rate is unpredictable and a cold cache behaves exactly like a missing model. The distinction between a cache and a precomputed read model matters: a read model is built for the query shape and has predictable performance, while a cache warms on first request and leaves some requests slow. If you need the degraded path to be dependable, build it as a read model, not as a cache.

The same reasoning applies to replication lag. If your degraded answer reads from a replica, it may be serving data that lags the primary by seconds or minutes, and applications reading from replicas must accept that the data is somewhat out of date. Decide up front whether a stale answer is better than a slow one for this endpoint. For a product listing, yes. For a balance check, no, and the ladder should end in an error rather than a wrong number.

A reference implementation

The ladder is a small amount of code. The important part is that the ordering comes from data, not from configuration order.

from dataclasses import dataclass

@dataclass
class Candidate:
    name: str
    price_usd: float
    p95_seconds: float

def expected_cost(c: Candidate, penalty_per_sec: float) -> float:
    return c.price_usd + penalty_per_sec * c.p95_seconds

def order_ladder(candidates, penalty_per_sec):
    return sorted(candidates, key=lambda c: expected_cost(c, penalty_per_sec))

def rest_of_ladder(remaining_budget_s, candidates, penalty_per_sec):
    ordered = order_ladder(candidates, penalty_per_sec)
    return [c for c in ordered if c.p95_seconds <= remaining_budget_s]

Two details make this production-ready. First, p95_seconds should be recomputed from real telemetry on a schedule, not typed in once. Second, rest_of_ladder is evaluated at the moment of the failure, with remaining_budget_s calculated from the request start, not from a fixed constant.

Test the ordering with synthetic traffic before trusting it. Send a share of requests through each rung and record which model actually served each one, the latency, and the cost. The difference between the planned ladder and the observed serving distribution tells you whether your penalty coefficient is realistic.

One caution on governance: a fallback chain that selects models outside an approved pool during an incident creates a compliance failure exactly when the system is already stressed. Fallback models should be pre-selected from the approved pool, not chosen at failure time. Fix the candidate set per workload class, then order within it.

Where the ladder sits relative to routing

A fallback ladder is a narrow tool. It handles one request whose primary call is slow or failing. It does not handle choosing between models for quality, splitting traffic by tenant, or negotiating cost across a fleet of workloads. Keep the layers separate so each one stays testable.

Cost discipline across the whole system starts earlier than the ladder. The largest drivers are model complexity, workload demand and infrastructure efficiency, and optimization should begin with baselines for cost, quality, latency and throughput before any routing change. The ladder is a latency tool with a cost tiebreak, not a general cost-optimization strategy. If your unit economics are wrong at the baseline, reordering fallbacks will not fix them.

The other structural point: a ladder that fans out to many providers needs a single place to hold credentials and see per-model spend. If you are building that routing layer yourself, one option is to put the provider surface behind a single key so the ladder only changes model identifiers. Quanta Inference exposes text, image, video, audio and embedding models through one OpenAI-compatible API, which keeps the ladder definition in your code rather than in five SDK setups.

Checklist before you ship the ladder

CheckWhat to verifyWhy it matters
Latency budgetEach endpoint class has an explicit numberGives the penalty its meaning
Penalty coefficientDollars per second derived from a real metricDecides the ordering at break-even
Measured latencyp95 from real traffic, cold path includedCold starts inflate the tail
Price basisPrice for your input/output shapePublished rates mislead on long prompts
Sort orderAscending expected cost, ties broken by latencyPrevents an expensive secondary
Budget filterCandidates that cannot finish are skippedInfinite cost is not a fallback
Degrade pathRead model or template, not a cold cacheMust be dependable under load
Approved poolFallbacks pre-selected, not chosen at failureAvoids compliance surprises
ObservabilityWhich rung served, latency, cost per eventLets you re-fit the numbers

Run this checklist when a new model is added to the candidate set, and again whenever the penalty coefficient changes. The ladder is a small model of your own economics. Like any model, it is only as good as the numbers you feed it, and those numbers come from your telemetry, not from a vendor's landing page.

FAQ

FAQ

Why not just order the ladder by price?

Because price ignores the cost of making the user wait. A cheap model with a slow tail can have a higher expected cost than a pricier model that always returns quickly, once you add the waiting penalty. The worked break-even in this article shows the exact penalty rate where the two orderings flip.

How do I set the penalty per second of waiting?

Derive it from a metric you already own: lost completions, retry rates, or support contacts per second of overrun, converted to dollars. Even a rough figure is enough to get the ordering right, because the decision only changes at the break-even point between two candidates. Revisit the coefficient when that metric moves.

Should I retry the primary before moving to the fallback?

Not for a slow call. A retry re-pays the primary's latency in full and usually produces the same slow result, leaving less budget for the rung that would have worked. Bounded retries make sense for transient network errors; latency overruns should go straight to the next rung, filtered by remaining budget.

What if no candidate fits the remaining budget?

Degrade the answer rather than escalate. Options in order of user impact: return the partial stream with a truncation note, serve a precomputed read model, fall back to a deterministic path, or return an honest error. A candidate whose p95 exceeds the remaining budget has effectively infinite expected cost for this request.

Does the ladder need to be the same for every endpoint?

No, and it should not be. A background enrichment job can tolerate a large budget, which pushes the penalty near zero and makes the ladder essentially a price sort. An interactive chat endpoint has a tight budget, which makes latency dominate. Define the ladder per workload class, with its own penalty coefficient and its own approved model pool.

Sources

Sources

  1. Low Latency AI Inference: How to Achieve It at Scale
  2. Limits | Supabase Docs
  3. Edge Functions limits
  4. What is Cold Start? Understanding Serverless Latency - PayPro Global
  5. Fallback Models for Reliable AI Inference
  6. The Difference Between a Cache and a Read Model, an ...
  7. Read Consistency with Database Replicas
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles