Article
Two routing providers and a straight line
A routing service has one obvious job: tell you how to get from here to there.
A delivery application gives it a less comfortable second job: keep answering when the service you normally ask has stopped answering.
The ecoeats router had a short fallback chain. Try OSRM. Try another routing provider. If both fail, calculate geographical distance and derive a rough duration.
It is pleasingly small code. It also hides an important change in the meaning of the answer.
Illustrative fallback model. No production endpoints, provider credentials, or operational thresholds are shown.
A successful return value can still be a worse answer
A road-aware estimate can account for the network being travelled. A straight-line distance cannot see a river, a one-way system, or the only bridge being in the opposite direction.
That doesn't make the approximation useless. It makes it an approximation.
The original abstraction returned distance and duration through a common interface. That kept callers simple, but a caller receiving only two numbers couldn't necessarily tell whether the result came from a route or a last-resort calculation.
This is where abstraction becomes a little too good at its job. It hides the implementation and the loss of information at the same time.
Preserve the distinction in the result
For a new version, I'd return provenance as part of the contract:
type Estimate = {metres: number;seconds: number;source: 'primary-route' | 'alternate-route' | 'geometric';quality: 'road-aware' | 'approximate';calculatedAt: string;};
This is a proposed design, not a claim about the historical API.
I prefer a meaningful quality category over a made-up percentage called confidence. A result labeled “83% confident” isn't more useful unless somebody can explain what that number predicts and how it was calibrated.
Now each caller can make a deliberate decision. A rough preview might accept an approximation. A hard delivery-area boundary or a customer promise may need a different fallback, a wider range, or no answer at all.
Give the fallback a budget
A fallback that starts after the request has already timed out isn't much of a fallback.
Suppose a fictional application has a 900 ms total budget. Spending all of it waiting for the first provider leaves nothing for the second. The deadline belongs to the overall operation, not independently to each attempt.
Propagate cancellation to provider requests where supported. Reserve enough budget for useful alternatives. If a provider is repeatedly failing, a circuit breaker can avoid making every customer wait for the same predictable failure.
Those mechanisms add complexity. They also address a different problem from simply catching an exception and moving on.
Not every error means try the next one
A timeout, a rate limit, invalid coordinates, and an authentication configuration error shouldn't all vanish into the same empty catch block.
Validate inputs before calling providers. Classify failures. A retry or alternate provider may help with temporary unavailability, but it won't make an impossible latitude sensible.
Transport mode also matters. A cycling request should not silently become a driving estimate merely because the fallback supports cars more conveniently. Either preserve the requested semantics or explicitly report that you cannot.
Similarly, the coordinate convention must be clear at every boundary. Swapping latitude and longitude can produce a confident answer about somewhere entirely unrelated to dinner.
Measure degradation, not just uptime
If the fallback always returns a number, your availability chart can look excellent while the quality of your answers gets steadily worse.
Count results by source. Track provider latency and classified failures. Watch the proportion of approximate results, and compare estimates with observed outcomes where you have suitable, privacy-conscious data.
Use synthetic coordinates in logs and examples whenever real locations aren't needed. Don't make route debugging a reason to collect an indefinite history of people's movements.
A useful test matrix includes provider timeout, malformed response, invalid input, unavailable transport mode, and disagreement between providers. Also test a route around an obstacle where a straight line is obviously misleading.
The fallback is a product decision
The engineering temptation is to make the function return something, anything, so the rest of the app can continue.
Sometimes that's right. Sometimes a clear “estimate unavailable” is better than a plausible number being used as a promise.
The right boundary is not between throwing and returning. It's between a result the caller understands and one that quietly means something else.
A straight line can keep the calculation alive. It cannot build a bridge.