DEV Community

RONI DAS
RONI DAS

Posted on • Originally published at systemdesign.academy

Designing Uber Eats: The Three-Sided Marketplace, Dispatch, and ETA

Food delivery looks like ride-hailing with a burger instead of a passenger. It is meaningfully harder, and understanding why is the key to the whole design. The difference comes down to timing: with a ride, the passenger is ready when the car arrives. With food, the meal is not ready, and the courier arriving too early wastes their time while arriving too late means cold food.

Three sides, not two

A ride-hailing marketplace has two sides, riders and drivers. Food delivery has three: the eater who orders, the restaurant that cooks, and the courier who delivers. All three have to be coordinated in time. The eater wants hot food fast, the restaurant wants orders paced so its kitchen is not slammed, and the courier wants to be moving, not waiting. The system's job is to keep all three roughly happy at once, and their interests conflict.

This three-sided structure shapes the data model. You track eaters and their orders, restaurants and their menus and current kitchen load, and couriers with live location and availability. The order itself moves through a state machine: placed, accepted by restaurant, being prepared, ready, picked up, delivered. Much of the engineering is making those state transitions reliable across three parties who can each fail or go offline.

The dispatch problem

Dispatch is the matching engine: which courier gets this order. In ride-hailing you assign the nearest available driver. In food delivery, nearest-now is the wrong answer, because the food will not be ready for fifteen minutes. If you assign the closest courier the moment the order is placed, they arrive in five minutes and stand around for ten. The right question is which courier will be near the restaurant when the food is ready.

That means dispatch has to predict preparation time and assign against the future, not the present. A courier finishing a nearby delivery in twelve minutes might be a better match than one who is free now. Good systems also batch: if two orders come from the same restaurant heading the same direction, one courier can carry both, which is more efficient for the platform and often barely slower for the eater. Batching is a bin-packing problem layered on top of matching, and it is where a lot of the margin lives.

The matching itself is a geospatial query. You need to find couriers near a point quickly, which rules out scanning every courier. The standard tool is a spatial index, often geohashing (encoding a lat-long into a string prefix so nearby points share a prefix) or an H3 hex grid, so "couriers within two kilometers" becomes a cheap lookup over a few grid cells rather than a distance computation across the whole fleet.

ETA: the number everyone sees

The delivery estimate is the most visible output and one of the hardest to get right, because it is a sum of uncertain parts: time to assign a courier, courier travel time to the restaurant, food preparation time, and travel time to the eater. Each has its own variability. Travel time depends on live traffic; prep time depends on the dish and how busy the kitchen is; assignment time depends on courier supply nearby.

The honest way to build this is a model per component, trained on historical data. Prep time is learned per restaurant and per dish and adjusted for current order volume. Travel time comes from a routing engine using live traffic. You sum the components and, importantly, communicate a realistic estimate rather than an optimistic one, because a late delivery against a promised time hurts more than a longer honest estimate.

The core trade-off

The tension is efficiency versus experience. Batching orders and assigning couriers against future readiness maximizes courier utilization and keeps costs down, but push it too far and food sits, couriers get overloaded, and eaters wait. Assign eagerly and generously and eaters are delighted while your unit economics collapse. The dispatch system is constantly tuning that dial, per city, per time of day.

How the real systems do it

Uber and DoorDash both run dispatch as an optimization that assigns against predicted readiness, not current position, and both batch orders when routes align. Geospatial indexing (Uber open-sourced H3 for exactly this) makes the "who is nearby" query fast. ETA is a composition of learned sub-models over prep and travel time. The recurring theme is that the hard part is time, not distance.

I wrote the full breakdown, with diagrams and the data model, here: https://www.systemdesign.academy/interview/design-uber-eats

Top comments (0)