Three right turns can be faster than one left turn.
That sentence annoyed me the first time I properly thought about it.
Not because it's difficult to understand. Quite the opposite. It sounds suspiciously like one of those business anecdotes that survives because it's memorable and nobody bothers checking whether it's actually true.
UPS trucks don't turn left.
Great story.
Also not true.
UPS trucks absolutely turn left. What UPS has done for years is try to reduce unnecessary left turns in countries where vehicles drive on the right.
The distinction is important.
A left turn across opposing traffic can leave a truck sitting at an intersection waiting for a gap. Sometimes that's a few seconds. Sometimes it means waiting through a signal cycle. The vehicle is consuming time and fuel while making precisely zero progress, and crossing traffic adds another source of accident risk.
For one truck, on one Tuesday afternoon, this is almost boring.
UPS doesn't operate one truck.
And that's where the arithmetic gets interesting.
The Shortest Route Has a Few Missing Columns
Imagine two routes to the same delivery.
One is 2.8 miles.
The other is 3.1.
If those are the only numbers available, there isn't much of a decision to make. Take the 2.8-mile route.
Now add two awkward intersections to the shorter route. Perhaps the truck needs to cross several lanes of opposing traffic. Perhaps one turn regularly takes 40 seconds during the delivery window.
The extra 0.3 miles no longer looks obviously wasteful.
Nothing about the distance changed. We simply started accounting for more of the cost.
UPS eventually turned this class of problem into ORION: On-Road Integrated Optimization and Navigation.
Calling ORION a route-planning system is accurate, although slightly underselling the problem. UPS needed to calculate useful routes for tens of thousands of drivers making large numbers of stops while respecting delivery commitments, pickup windows, road conditions and the awkward realities of getting a large brown truck to the correct side of a building.
The mathematics gets ugly quickly.
A driver with around 120 stops doesn't have 120 possible routes. The number of possible sequences is astronomically larger. You can't enumerate everything, find the perfect route and go home early.
And even a mathematically attractive route can be operationally ridiculous.
An address might technically be on a road while the receiving dock is around the back. A route that works nicely at 9 AM may become painful when school traffic appears. A particular sequence may force the driver to cross the same busy road repeatedly.
This is what makes ORION more interesting than the left-turn trivia.
UPS wasn't solving geometry.
It was trying to model reality closely enough to make better decisions inside it.
Six Miles Doesn't Sound Like Much
During ORION's rollout, UPS reported reducing routes by roughly six to eight miles in areas where the system had been deployed.
Six miles.
If somebody told you they had spent years developing algorithms, collecting location data, changing driver workflows and deploying a large optimization platform so one vehicle could drive six fewer miles, you might reasonably wonder whether the project had escaped from a PowerPoint presentation.
Then multiply it.
UPS projected that full deployment could eliminate roughly 100 million miles of driving and save around 10 million gallons of fuel annually.
That's the part of the story I like.
There isn't one spectacular optimization hiding somewhere.
One truck saves a little.
Then another does.
Then tens of thousands of trucks do it again tomorrow.
Software engineering has a weakness for dramatic optimization. Replace the database. Rewrite the service in Rust. Introduce a new platform. Split the monolith. Announce an architecture initiative with a name that sounds faintly military.
Those things are occasionally exactly what a system needs.
But high-volume systems also contain much less glamorous opportunities.
Remove a small allocation from a path executed hundreds of millions of times. Avoid serializing data nobody reads. Eliminate one unnecessary network round trip from a hot request. Improve a cache hit rate slightly on something called constantly.
None of these makes an exciting conference keynote.
At sufficient scale, they don't need to.
And this was roughly where the UPS story started bothering the software engineer in me.
We Do This Constantly
Suppose I put two backend designs in front of you.
The first makes three network calls.
The second makes five.
Which one is faster?
Three feels like the sensible answer.
But suppose those three calls are sequential and one goes to an external provider. The five calls are internal, independent and executed concurrently.
Now I haven't given you enough information.
We need to know actual latency, whether connections are reused, failure rates, retry behaviour, rate limits, payload sizes and what those dependencies look like when they're having a bad afternoon.
Counting calls wasn't useless. It just wasn't enough.
Microservice diagrams make this particularly easy to forget because every dependency becomes an arrow.
An API calling Redis and an API calling a third-party payment provider may each occupy roughly the same amount of ink.
Operationally, they're very different dependencies.
The external call crosses infrastructure you don't own. It can time out. It may be rate-limited. Its latency distribution can change without your deployment changing at all. When it fails, retries have to be designed carefully because an unhealthy dependency doesn't usually recover faster when all of its customers enthusiastically send it additional traffic.
“Three calls instead of five” tells me almost nothing about that.
Yet architecture discussions regularly treat these counts as evidence.
We do the same thing with services, queues, database queries and deployment steps because they're visible and easy to count.
Easy to count is not the same thing as expensive.
My Favourite Version Is “We Made It One Query”
Database optimization produces a particularly good version of this mistake.
An endpoint makes six queries.
Someone notices.
“We can make this one query.”
Possibly.
And sometimes that's a very good change.
Other times the result is a magnificent SQL statement with enough joins, CTEs, aggregations and conditional logic that EXPLAIN ANALYZE starts looking like a genealogy project.
Still, six became one.
The dashboard in our heads turns green.
The problem is that databases don't invoice us by query count.
Six small indexed lookups can be perfectly reasonable. One badly planned query over large tables can be disastrous. The opposite can also be true: six network round trips may be unnecessary when the database could efficiently do the work once.
You have to look.
Execution plans matter. Cardinality matters. Index selectivity matters. Data volume matters. Locking matters. Round-trip latency matters. What happens when twenty requests execute the same query concurrently matters quite a lot.
This sounds obvious when written down.
It becomes surprisingly non-obvious during optimization work because reducing a visible number feels like progress.
I've become suspicious of improvements whose strongest argument is that one number got smaller.
Not dismissive. Suspicious.
There is a difference.
The One-Arrow Architecture
The same problem gets more expensive once services start depending on each other.
Service A needs information owned by Service B.
The straightforward implementation is:
Service A → Service B
Done.
One dependency. Easy to explain. Easy to draw.
Then B becomes slow.
Now A becomes slow.
B occasionally becomes unavailable, so A starts failing too. We add retries because optimism is apparently an infrastructure pattern.
The retries help with transient failures until B experiences a larger incident, at which point several callers simultaneously decide that the struggling service would benefit from additional requests.
So we add exponential backoff. Maybe circuit breaking. Perhaps caching. We have to think about timeouts properly. Suddenly our one-arrow architecture contains quite a lot of behaviour that isn't visible in the arrow.
There may be another design.
B publishes relevant changes asynchronously. A consumes them and keeps the small piece of state it needs locally.
The request path becomes independent of B, but we've paid for that independence elsewhere. There is a broker now. Event schemas need ownership. Consumers need monitoring. Replay needs to work. Someone has to decide what eventual consistency means for the business operation rather than merely writing the phrase in an architecture document and hoping everybody finds it reassuring.
This is not automatically better.
If the information must be strongly consistent, the asynchronous design may be wrong. If A requests the data twice a week, it is almost certainly unnecessary.
We should not deploy Kafka every time two services make eye contact.
But there are workloads where the design containing more components produces a simpler runtime failure model.
That's a difficult trade-off to see if “fewer boxes” is being used as a proxy for simplicity.
Some complexity comes from our architecture.
Some complexity comes from the problem.
Deleting the former is useful. Pretending the latter disappeared because we removed a box from a diagram tends to end less well.
ORION Had Another Input: Drivers
One part of the ORION story tends to disappear when it gets reduced to “UPS avoids left turns.”
The drivers already knew a lot.
A driver who has served the same area for years knows things that are difficult to infer from an address database. Which entrance actually works. Where the receiving dock is. Which road becomes unpleasant at a particular hour. Where turning around a large vehicle is technically possible but strategically regrettable.
Meanwhile, an optimization system can evaluate combinations and patterns that no driver could reasonably calculate during a shift.
UPS spent years field-testing ORION rather than simply dropping an algorithm into trucks and declaring mathematics victorious. Its navigation systems became increasingly precise about actual delivery and pickup locations.
That feels familiar.
The first version of an architecture is based on what we know about the system before operating it.
Then production contributes its review comments.
A retry policy behaves differently under a real outage. A cache introduces an invalidation problem nobody cared about in the prototype. A dependency with perfectly acceptable average latency develops a horrible tail. A queue absorbs traffic beautifully until the consumer falls behind for six hours and recovery becomes the real problem.
This doesn't necessarily mean the design was bad.
It means production supplied information the original model didn't contain.
The important part is what happens next.
If measurements disagree with our assumptions, the assumptions don't get seniority.
The Fast Service That Takes Four Seconds
Latency is probably where software engineers encounter this problem most often.
Imagine two services.
The first usually responds in 80 ms but occasionally takes four seconds.
The second tends to respond around 130 ms and rarely exceeds 220 ms.
Which one would you rather put in a checkout path?
There isn't a universal answer, but I know which set of graphs I'd want to inspect before choosing.
Average latency can make the first service look fantastic. Median latency might too.
Customers eventually meet the tail.
It gets worse when requests compose several dependencies. Your endpoint can be individually well behaved while inheriting somebody else's P99, and enough downstream calls give a request several opportunities to encounter an outlier.
This is why I find optimizations focused entirely on averages increasingly difficult to trust.
Sometimes predictability is worth paying a little latency for.
The requirement isn't always “make the fastest request faster.”
Quite often it's “make the bad requests stop being terrible.”
Those are different engineering projects.
So What Are We Actually Optimizing?
This question should probably appear earlier in more performance discussions.
Someone says:
“We need to optimize this.”
Okay.
What is expensive?
CPU? Database time? Network latency? Cloud spend? Failure rate? Human intervention? Customer waiting time?
Those can point toward completely different changes, and improving one can make another worse.
A cache might reduce latency while introducing stale-data behaviour.
Parallel calls can reduce response time while increasing instantaneous pressure on downstream systems.
More replicas improve capacity and resilience while costing money.
A queue can isolate failures but adds another operational system and changes when work becomes visible to the caller.
None of this is a reason to avoid optimization. It's a reason to be precise about what success means before changing the architecture.
UPS could have optimized purely for mileage.
It didn't, because a delivery operation doesn't make money by winning a shortest-distance competition.
Software systems don't either.
Sometimes the Longer Route Is Just Longer
There is a danger in getting too enthusiastic about this idea.
Once engineers discover that the obvious optimization can be wrong, we occasionally become suspicious of anything simple.
That's how a service that needs to store three settings ends up with Kafka, Redis, CQRS and an architectural decision record explaining its “future scalability posture.”
Sometimes the shortest path really is best.
Sometimes one query is better than six.
Sometimes removing a service reduces both latency and operational burden.
And sometimes the left turn is completely empty and you should just turn left.
The point isn't to prefer complicated routes.
It's to stop assuming that distance and cost are interchangeable.
Before optimizing something, I now want to understand three things reasonably well: what we're paying for, how often we're paying for it, and what the proposed improvement makes worse.
That usually gets us much further than starting with “How do we reduce the number of X?”
Especially when X happens to be the easiest thing on the dashboard to count.
The Part Worth Stealing From UPS
The famous version of the UPS story is wonderfully simple:
Avoid left turns.
I don't think that's the useful version.
The useful version is that UPS spent years figuring out what a mile actually costs inside its operation.
Not theoretically. Operationally.
A mile driven at speed isn't equivalent to a mile spent crawling through traffic. A short route with awkward intersections isn't necessarily cheaper than a slightly longer one. A mathematically elegant sequence isn't particularly valuable if the driver can't execute it efficiently.
Once you see the problem that way, the left-turn rule stops looking quirky. It's simply one consequence of having a better understanding of cost.
That's the part worth carrying into software.
We spend a lot of time making systems smaller on paper.
Fewer calls. Fewer queries. Fewer services. Fewer components.
Often that's exactly the right thing to do. Simplicity deserves a strong default.
But the number getting smaller isn't proof that the system got better.
A network call disappears, but perhaps the data now has to be synchronized somewhere else. Six queries become one, but perhaps that query behaves terribly once the table has 200 million rows. A service gets removed, but its responsibilities haven't vanished; they've just moved into another service that nobody wanted to make larger.
The cost didn't disappear because the architecture diagram became prettier.
And I think that's why the UPS story has stuck with me.
It isn't really a story about left turns.
It's about choosing the right thing to optimize.
The shortest route is obvious when you're looking at a map. The better route only becomes obvious when you understand what actually happens while driving it.
UPS figured out that driving a little farther can sometimes get you there faster.
In software, we keep learning the same lesson.
Usually right after deploying the shorter route to production!
Top comments (0)