Article

Your dinner is arriving in five minutes. Again.

Working on ecoeats meant dealing with the uncomfortable fact that a delivery estimate isn't one calculation. It's a chain of predictions about people, kitchens, vehicles, and front doors. The route is only part of it.



Conceptual illustration. The scenarios and model below are simplified, not historical performance measurements.

Before the rider moves, the map isn't the main problem

A distance service can estimate travel time between two points. It cannot tell you whether the food is ready, whether somebody has accepted the collection, or whether the customer will answer the door.


Our estimate-review code treated orders differently according to their current stage. Work still being prepared needed different reasoning from work travelling to the customer. The system also considered situations where the expected collection time had passed or a rider wasn't yet in place.


That distinction is the core of a useful model: ask what remains uncertain at this stage, not just how far away the address is.

Two clocks have to meet

For a simplified single-stop delivery, collection can't happen until both the food and the rider are ready.


possible collection time = max(food ready time, rider arrival time)
possible delivery time = collection + handoff + onward travel

This is a conceptual model, not the original algorithm. It ignores batching and several other complications. But it makes one important dependency visible.


If the rider arrives before the food, shaving another minute off the route to the restaurant changes nothing. If the food is ready but no rider is available, a better kitchen estimate doesn't solve that either.


Optimising the wrong component can produce a very precise answer to the wrong question.

A missing observation is not fresh evidence

Suppose collection was expected at 18:30. At 18:31, we still haven't observed it.


Moving the estimate forward is understandable. But adding a small delay every time a background job runs isn't the same as learning when the food will actually be ready. It is acknowledging that the previous estimate failed.


I'd keep those ideas separate. A revised kitchen readiness signal is new evidence. A rider's recent location is new evidence. “The old estimate is now in the past” tells us less, even though we still need to respond to it.


The interface should not communicate all three with the same confidence.

Give the estimate a reason

An estimate object can carry more than a timestamp. Here's a proposed shape for a new implementation:


type DeliveryEstimate = {
earliest: string;
latest: string;
updatedAt: string;
reason: 'preparing' | 'awaiting-rider' | 'travelling' | 'handoff';
evidence: 'recent-observation' | 'fallback';
};

Those fields don't magically make the model accurate. They make its assumptions inspectable.


A range shouldn't be arbitrary decoration either. If you display an interval, measure how often deliveries fall inside it. Otherwise you've simply replaced one unjustified number with two.


For users, a short explanation such as “The restaurant is still preparing your order” can be more useful than a newly confident countdown. Don't expose a stream of internal state changes; explain the change that matters to the person waiting.

Don't turn GPS into a lie detector

Once a rider is travelling, location and routing become much more useful. They still aren't ground truth.


Location can be stale, imprecise, or unavailable. Arrival near a building isn't proof of handoff. A difficult entrance can consume time that the road network doesn't represent.


Keep the age of a location observation alongside its coordinates. Distinguish “arrived nearby” from “delivered”. Treat unexpected timing as a signal to investigate, not automatic proof that a person did something wrong.


The application should help operations understand uncertainty rather than burying it under a decimal place.

Judge the estimate when it was shown

A final estimate updated seconds before delivery can look excellent in a report. That doesn't help the customer who saw something wildly different when ordering.


I'd evaluate predictions by when they were made: at checkout, after acceptance, after collection, and near arrival. Track absolute error, how often promised windows are missed, and how frequently the estimate changes.


Also separate stages. An excellent travel estimate can coexist with a terrible overall promise if preparation or assignment is the real source of delay.


A sensible test suite includes delayed preparation, no available rider, stale location, cancellation, and a customer handoff that takes longer than expected. Use a controllable clock so these tests don't require somebody to wait around hungry.

Less certainty, more honesty

There will always be situations where the system doesn't know enough. The engineering task isn't to eliminate that fact. It's to stop disguising it.


A good estimate is updated when the world changes, carries an understandable reason, and becomes more precise as the remaining work becomes more observable.


If the food isn't ready, the honest answer isn't necessarily another five minutes. Sometimes it's admitting that the kitchen knows something your map doesn't.

Made with Intenso