Uber Eats System Design Interview: Dispatching Couriers and Predicting ETAs on a Three-Sided Marketplace
Uber Eats connects three parties at once: eaters browsing for food, restaurants preparing orders, and couriers moving through traffic. The dispatch layer evaluates a very large number of possible order-to-courier assignments continuously, and Uber has said its matching engine generates on the order of tens of millions of match-pair predictions per minute across its mobility and delivery products (Uber engineering, estimate for the combined platform). Every order stitches together a geospatial search, a food-prep-time prediction, a travel-time prediction, and a live tracking stream, and it all has to feel instant on a phone.
Uber Eats is a three-sided real-time marketplace. Eaters open the app at a delivery address and expect a ranked list of nearby restaurants that can actually deliver to them quickly, which makes search fundamentally geospatial rather than purely lexical. When an order is placed, the platform runs a distributed transaction across payments, the restaurant point-of-sale, and courier dispatch, then keeps everyone updated with a live map. The hardest parts are dispatch and timing: the system has to predict how long the food will take to cook, when a courier will reach the restaurant, and how long the final leg to the eater will take, then solve a global assignment problem that pairs orders with couriers to minimize total wait and keep food hot. On top of that sit geo-sharded search indexes, streaming location pipelines, surge pricing during demand spikes, and a payment split that pays the restaurant, the courier, and captures Uber's fee. A good design separates the read-heavy discovery path from the write-heavy order and dispatch path, and treats ETA prediction as a first-class machine learning system rather than a fixed constant.
Asked at: Asked at Uber, DoorDash, Grubhub, Deliveroo, Amazon, and most large marketplace and logistics companies. It is a favorite because it combines geospatial systems, real-time matching, ML-driven predictions, and a multi-party distributed transaction in one problem.
Why this question is asked
Interviewers like this problem because it refuses to collapse into a single well-known pattern. A candidate has to reason about a three-sided marketplace where supply (couriers) and demand (orders) are both moving and both perishable, since a courier idle for ten minutes and food going cold both cost money. It tests geospatial indexing, streaming ingestion of GPS, a real optimization or matching algorithm, machine learning for ETA, and the classic distributed-transaction question of how to place an order across payments, the restaurant, and dispatch without leaving the system in a half-committed state. It also rewards candidates who can separate the cheap, cacheable read path from the expensive, consistency-sensitive write path, and who can talk about degradation when a region gets hot.
Requirements
Always clarify these in the first 5 minutes of the interview. Do not start drawing boxes until both lists are agreed.
Functional requirements
- Eaters can search and browse nearby restaurants and stores for a given delivery address, filtered by what can actually deliver to them.
- Eaters can view menus, add items to a cart, and place an order with payment.
- The system predicts and shows an estimated delivery time before and after the order is placed.
- Restaurants receive orders, confirm them, and signal preparation progress.
- The platform dispatches an available courier to each order and can batch multiple orders onto one courier when efficient.
- Eaters, restaurants, and couriers see live order state and a real-time map of the courier's location.
- The system charges the eater once, then splits the payout to the restaurant and the courier and captures Uber's service fee.
- Surge or busy-area pricing adjusts delivery fees when demand outstrips available couriers.
- Eaters can rate the order and courier, and the system supports refunds and cancellations.
Non-functional requirements
- Search results should return in a few hundred milliseconds even in dense cities.
- Dispatch decisions must be made within seconds of an order being confirmed so food does not sit.
- Live location updates should propagate to the eater's map within a couple of seconds.
- Order placement and payment must be exactly-once from the eater's perspective; no double charges.
- The system must stay available regionally; an outage in one city must not take down others.
- ETA predictions should be accurate enough to keep both courier idle time and cold-food incidents low.
- The platform must scale elastically for daily peaks at lunch and dinner and for events.
- Personal and payment data must be handled per regional privacy and PCI requirements.
Back-of-envelope scale estimates
Show your math. Pulling numbers from thin air signals you have not thought about the load.
Daily orders
~15-25M orders/day (estimate)
Uber has publicly reported Delivery gross bookings in the tens of billions of dollars per year. Assuming an average order value near 30 to 35 dollars, annual order volume lands in the billions, which divided by 365 gives roughly 15 to 25 million orders per day. Derived, order of magnitude only.
Peak order rate
~1,700-3,000 orders/sec at dinner peak (estimate)
20M orders/day is ~230/sec averaged over 24h, but demand is spiky and concentrated in lunch and dinner windows. Applying a peak-to-average factor of 8 to 12 for those windows gives roughly 1,700 to 3,000 orders per second. Derived estimate.
Active couriers emitting GPS
Hundreds of thousands concurrently, pings every ~4s (estimate)
With hundreds of thousands of couriers online at peak and each phone sending a location update every few seconds, the location pipeline ingests on the order of 10^5 writes per second. This is the highest-volume write stream in the system. Derived estimate.
Search queries
Billions of queries/day across verticals
Uber engineering has described the search stack as serving billions of daily queries across restaurants, grocery, and retail. Browsing generates far more queries than orders because most sessions do not convert. Reported by Uber, exact split not public.
Match-pair predictions
Tens of millions per minute (platform estimate)
Dispatch evaluates many candidate courier-order pairings per decision, scored by predicted travel and prep times. Uber has cited tens of millions of match predictions per minute for its combined matching engine. Reported for the platform, not Eats alone.
Restaurant and store catalog
Millions of merchants, tens of millions of menu items (estimate)
Uber Eats operates across thousands of cities with growth into grocery and retail. Assuming millions of active merchants and an average of tens to hundreds of items each yields tens of millions to low billions of indexed documents. Derived estimate.
High-level architecture
The read path and the write path are deliberately different systems. On the read path, an eater opens the app with a delivery location. The client hits an API gateway that routes to a search service, which resolves the address to an H3 hexagon and queries a geo-sharded, Lucene-based restaurant and store index scoped to the hexagons that can deliver to that point. A first-pass ranker does cheap lexical and geo filtering on the data nodes, then results are hydrated with per-store ETAs, promotions, and availability, and a second-pass personalized ranker orders them using conversion history and business signals. Menus and store metadata are served from caches because they change slowly. On the write path, placing an order kicks off an order service that acts as an orchestrator: it authorizes payment, sends the order to the restaurant's point-of-sale or tablet, and hands the order to dispatch. Dispatch is where the real-time work happens. A prediction layer estimates food prep time, courier travel time to the restaurant, and travel time from restaurant to eater, and a matching engine solves a global assignment of open orders to nearby couriers to minimize total time and idle. Couriers stream GPS continuously into a location pipeline built on Kafka-style streams; those positions feed both the matching engine and the live tracking map that eaters and restaurants watch. State transitions of the order flow through an event log so that timeouts, retries, and compensation can be handled cleanly, and so analytics and ETA models can learn from the completed trips.
In a real interview, sketch this on the whiteboard before diving into any single box.
Core components
Walk through each service. The interviewer wants to hear what each one owns, not just the names.
Geospatial search and discovery service
Resolves the eater's delivery address to H3 cells and queries geo-sharded search indexes for stores that can deliver there. It runs multi-stage retrieval and ranking, first cheap geo and lexical filtering on data nodes, then hydration and a personalized second-pass rank. It is read-heavy and heavily cached.
Order orchestration service
Owns the order lifecycle as a state machine from cart to delivered. It coordinates payment authorization, restaurant acceptance, and dispatch as a saga, issuing compensating actions such as refunds if a downstream step fails. It is the source of truth for order state.
Dispatch and matching engine
Continuously pairs open orders with available couriers by solving a global optimization rather than greedily matching one order at a time. It scores candidate pairs using predicted prep and travel times and can batch several orders onto one courier when their routes align. This is the latency-sensitive heart of the platform.
ETA and time-prediction service
A set of machine learning models that predict food preparation time, courier arrival time at the restaurant, and the final delivery leg. Prep time uses gradient-boosted trees over order and restaurant features, and travel time uses deep models like DeepETA on top of a routing engine. These predictions drive both dispatch timing and the ETA the eater sees.
Real-time location pipeline
Ingests high-frequency GPS pings from every active courier through a streaming system, cleans and map-matches them, and fans the positions out to the matching engine and to live tracking. It is the highest-write-volume component and is tuned for throughput and low end-to-end latency.
Payments and payout service
Charges the eater once with an idempotency key, then splits funds into a restaurant payout, a courier payout, and Uber's service fee, applying surge and tips. It integrates with external payment processors and must reconcile money movement exactly, so it leans on an event log and ledger rather than in-place updates.
Pricing and surge service
Computes delivery fees and busy-area multipliers from live supply and demand within each geographic zone. When couriers are scarce relative to orders in a hexagon cluster, it raises fees to balance the marketplace and protect delivery times. It reads from the same location and order streams that dispatch uses.
Data model
Pick the right store per table. Justify each choice with the access pattern, not by reflex.
restaurantsrestaurant_idnamegeo_pointh3_celldelivery_zonestatusStore metadata and location, indexed by H3 cell so search can scope queries to hexagons that can deliver to the eater. Slowly changing, so it is aggressively cached and served from a read-optimized search index.
menu_itemsitem_idrestaurant_idnamepricecategoryavailableMenu catalog keyed by restaurant. Availability toggles frequently during service. Denormalized into the search index sorted city then store then item to reduce retrieval latency.
ordersorder_ideater_idrestaurant_idcourier_idstatecreated_atThe order state machine record, partitioned by region. state moves through created, confirmed, preparing, courier_assigned, picked_up, delivered, or canceled. Transitions are appended to an event log for auditing and recovery.
courier_locationscourier_idlatlngh3_cellheadingtsThe hot, high-write stream of GPS pings. Latest position is kept in an in-memory geospatial store keyed by H3 cell for fast nearby-courier lookups; the full history flows to the analytics lake for model training.
dispatch_offersoffer_idorder_idcourier_idpredicted_etascorestatusRecords each courier-order pairing the matching engine proposes, with the predicted times that justified it and whether the courier accepted. Feeds back into ETA and matching model retraining.
paymentspayment_idorder_ididempotency_keyamountrestaurant_payoutcourier_payoutfeeOne charge per order enforced by idempotency_key, with the split recorded as ledger entries. Money movement is modeled as immutable events so reconciliation and refunds are auditable rather than destructive.
Deep dives
These are the conversations the interviewer is steering you toward. Practice each one until you can talk through it without notes.
Geospatial search: why hexagons and how the index is sharded
Search on Uber Eats is not text-first, it is location-first, because the only stores worth returning are the ones that can deliver to the eater's exact point. Uber models the world with H3, its open-source hexagonal hierarchical spatial index, which tiles the globe into hexagons at multiple resolutions. Hexagons are preferred over squares because every neighbor sits at an equal distance from the center, whereas a square grid's diagonal neighbors are about 1.41 times farther than its edge neighbors, which distorts distance and density math. A delivery address is converted to an H3 cell, and the query is scoped to that cell and its ring of neighbors that fall inside deliverable range. The search index itself is geo-sharded. Uber has described two strategies, latitude banding, which spreads load across time zones but leaves dense cities lumpy, and hex sharding, which bin-packs H3 cells into balanced shards with better cache locality. Stores near a shard boundary are indexed into multiple shards as a buffer so results are not cut off at the seam. This geo-scoping is what keeps a query fast even though the global catalog holds tens of millions of documents.
The dispatch problem: greedy versus global matching
The naive way to assign couriers is greedy: take each order as it arrives and hand it to the closest free courier. That is simple and locally reasonable but globally wasteful, because a courier grabbed for one order might have been the ideal choice for a nearby order that arrives a second later. Uber moved from greedy to a global matching approach that treats the current set of open orders and available couriers as a single optimization problem, minimizing combined metrics like total travel time and courier idle time. Uber's own example shows global matching cutting combined travel from six minutes to four by swapping two assignments. The engine scores each candidate courier-order pair using predicted prep and travel times, and it can batch multiple orders that share a route onto one courier to raise efficiency. Because the marketplace is constantly changing, this is not solved once but re-solved on a short cadence over a moving window of supply and demand. The output is a set of offers that couriers can accept, and acceptance and rejection feed back into future matching.
Predicting three clocks: prep time, arrival time, and delivery time
A good ETA is really three predictions stitched together, and each is its own model. Food preparation time is predicted with gradient-boosted decision trees trained in XGBoost over features like item count, cuisine, restaurant history, and time of day, which is far better than assuming a flat 25 minutes for every order. Courier travel time to the restaurant and from the restaurant to the eater comes from routing plus learned corrections; Uber built DeepETA, a low-latency deep neural network that sits on top of a routing engine and corrects its estimates using live traffic and contextual features, served globally with tight latency budgets on the Michelangelo ML platform. Uber also trains separate travel models for bikers and walkers, who avoid congestion differently than cars. The hard part is that restaurants rarely report when food is actually ready, so there is no clean ground truth for prep time. Uber infers it from courier sensor data and closes the loop by correcting the inferred prep time as new signals arrive, then retraining on the corrected labels.
Timing dispatch with the trip state model
Dispatching a courier the moment an order is confirmed is often wrong. Send the courier too early and they stand around while the kitchen cooks, wasting supply; send them too late and the food sits under a heat lamp going cold. Uber built a Trip State Model that segments a courier's journey into discrete states such as arrived at restaurant, parked, waiting at restaurant, walking back to the vehicle, and en route to the eater. Plain GPS is too noisy in dense urban areas to tell these apart, so Uber fuses GPS with phone motion sensors and activity-recognition signals, and models the sequence with a conditional random field that captures the natural ordering, for example that a courier can only wait at the restaurant after parking. By learning restaurant-specific distributions of time spent in each state, the system can time the dispatch so the courier arrives about when the food is ready. This tightens the gap between prep completion and pickup, which is the single biggest lever on both courier idle time and food freshness.
Live tracking and the location streaming pipeline
Every eater watching a little car crawl toward their house is looking at the top of a large streaming system. Hundreds of thousands of courier phones each emit a GPS ping every few seconds, which is the heaviest write stream in the platform, on the order of 100k writes per second at peak as an estimate. Those pings flow through a Kafka-style pipeline where they are cleaned, map-matched to roads, and written to an in-memory geospatial store keyed by H3 cell. Two consumers read that stream. The matching engine uses current courier positions to find nearby candidates and to keep ETAs fresh. The live-tracking service pushes updates to eater and restaurant apps over persistent connections so the map moves within a second or two. The design keeps the hot, latest-position lookup separate from the durable history: latest positions live in a fast key-value or in-memory geospatial store for reads, while the full trace is archived to the data lake to train the ETA and trip-state models. This read-write separation is what lets the system serve fast lookups and heavy analytics from the same firehose.
Order placement as a distributed transaction
Placing an order touches at least three systems that cannot share one database: the payment processor, the restaurant's point-of-sale, and dispatch. A two-phase commit across external processors and third-party tablets is not realistic, so the order service runs a saga. It authorizes payment first, sends the order to the restaurant, and then engages dispatch, and if any step fails it fires compensating actions, for example voiding or refunding the payment and notifying the eater. Idempotency is essential throughout: the eater's charge carries an idempotency key so a retried request never double-charges, and the order id makes downstream calls safe to repeat. Because a restaurant can reject an order after payment is authorized, the flow uses authorize-then-capture rather than an immediate charge, capturing only once the restaurant accepts. Modeling the order as a sequence of events rather than a single mutable row means the system can always replay how it reached its current state, which matters for support, refunds, and reconciliation.
Surge and marketplace balancing under load
Demand for food is bursty and geographically concentrated, and courier supply cannot expand instantly. When orders in a cluster of hexagons outpace the couriers available there, delivery times would balloon and food would arrive cold unless the marketplace rebalances. The pricing service watches the same live order and courier streams that dispatch uses, computes a supply-to-demand ratio per zone, and raises delivery fees or applies a busy-area multiplier when supply is tight. Higher pay pulls more couriers toward the hot zone and gently dampens demand, pushing the ratio back toward balance. This has to be computed per small geographic area and updated continuously, which is why it is built on the streaming layer rather than on batch jobs. Under extreme load the platform also degrades gracefully: it can widen or narrow the search radius, cap the candidate pool for ranking to protect latency, and prioritize keeping existing orders on track over accepting new ones. Getting this balance wrong is expensive, since one early experiment doubling the ranking candidate pool from 200 to 400 documents caused a 4x jump in P50 latency.
Trade-offs to discuss
Every senior interviewer expects you to surface at least 3 of these. Pick the decisions, state the alternatives, and justify your choice.
Global matching versus greedy nearest-courier assignment
Greedy is simpler and lower latency but leaves efficiency on the table by committing couriers before better pairings are known. Global matching over a short window produces meaningfully lower total travel and idle time at the cost of a heavier, repeatedly solved optimization. For a marketplace where every minute of idle and every cold meal has real cost, the global approach wins, and the platform accepts the extra compute.
Hex sharding versus latitude banding for the search index
Latitude bands are trivial to reason about and spread traffic across time zones, but they leave dense cities packed into a few hot shards. Hex sharding bin-packs H3 cells into balanced shards with better cache locality at the cost of more complex routing and boundary buffering. Uber favors hex sharding where balance and locality matter most, and pays for it with buffer-zone duplication near shard edges.
Learned ETA models versus a fixed prep-time constant
A constant like 25 minutes is easy and predictable but consistently wrong, which desynchronizes dispatch and erodes trust in the ETA. Machine-learned prep and travel models are far more accurate but require a training pipeline, feature engineering, and inference infrastructure, plus inferred labels because restaurants do not report readiness. The accuracy gain is worth the operational weight because timing quality drives the whole marketplace.
Saga with compensation versus two-phase commit for order placement
Two-phase commit would give clean atomicity but is impractical across external payment processors and third-party restaurant tablets, and it blocks resources while coordinating. A saga with authorize-then-capture and compensating refunds keeps each service autonomous and available, accepting eventual consistency and the need to design idempotent, reversible steps. For loosely coupled external systems, the saga is the realistic choice.
Separate hot latest-position store versus a single database for locations
Serving nearby-courier lookups and live maps from the same store that holds full GPS history would couple fast reads to heavy write and analytics load. Splitting into an in-memory geospatial store for latest positions plus a durable lake for history adds moving parts and a consistency gap, but it keeps lookups fast and lets analytics scale independently. The separation is standard for high-volume location systems.
Batching multiple orders per courier versus one order per trip
Single-order trips are simplest and give the fastest possible delivery for that one eater, but they underuse courier capacity in dense areas. Batching orders that share a route raises efficiency and lowers cost per delivery at the risk of slightly slower delivery for the first eater in the batch. The system batches only when predicted routes align closely enough that the added delay stays small.
How Uber Eats actually does it
The details here follow Uber's own engineering writing. Uber open-sourced H3, its hexagonal hierarchical spatial index, and uses it to shard delivery zones and power features like Orders Near You. Its dispatch system moved from greedy to global matching, and its time predictions run on the Michelangelo ML platform, with food prep time modeled by XGBoost gradient-boosted trees and travel time by DeepETA, a deep network that corrects a routing engine's estimates under a tight global latency budget. Because restaurants do not report when food is ready, Uber infers prep time from courier sensor data using a conditional random field trip-state model that fuses GPS with phone motion and activity signals, then closes the loop by correcting inferred labels and retraining. The search stack is a multi-stage Lucene-based system with Kafka ingestion, geo-sharding, and two-pass ranking, and Uber has publicly discussed how naive changes like doubling the ranking candidate pool caused large latency regressions. Numbers for daily orders, peak rates, and location write volume in this write-up are labeled estimates unless attributed to Uber.
Sources
- Uber Blog: How Trip Inferences and Machine Learning Optimize Delivery Times on Uber Eats
- Uber Blog: DeepETA, How Uber Predicts Arrival Times Using Deep Learning
- Uber Blog: H3, Uber's Hexagonal Hierarchical Spatial Index
- Uber Blog: Orders Near You and User-Facing Analytics on Real-Time Geospatial Data
- InfoQ: Predicting Time to Cook, Arrive, and Deliver at Uber Eats
Lessons to study before this interview
If any of these topics are fuzzy, the interviewer will catch it. Each lesson is 15 to 60 minutes with diagrams, code, and a quiz.
Related system design interview questions
Practice these next. They lean on the same core building blocks as Uber Eats.