HomeBlog › Design DoorDash

Design DoorDash — A Food Delivery System Design Walkthrough

A senior-level walkthrough of the three-sided marketplace: the order state machine, prep-time-aware dispatch, batching, and the ETA you have to promise before the food is even cooked.

"Design DoorDash" looks like a ride-hailing question with food attached, and that assumption is the fastest way to give a shallow answer. Ride-hailing is a two-sided marketplace: match a rider to a nearby driver, and the trip begins the moment the driver arrives. Food delivery is a three-sided marketplace — customer, merchant, courier — and it inserts a production step that nobody on the platform controls: the restaurant has to actually cook the food. If you want the pure matching-and-tracking version of this problem, read Design Uber; this walkthrough deliberately spends its time on what food delivery adds. Keep the system design interview cheat sheet nearby for the shared primitives, and mirror this structure in a real system design interview.

That third side changes almost everything downstream. Dispatch cannot simply grab the closest courier, because a courier who arrives ten minutes before the food is ready is a courier standing idle during the dinner rush. The ETA is not one travel estimate but a sum of legs, one of which is a kitchen. And unlike a rider, a bag of food can share a vehicle — so one courier can carry several orders at once, which is where most of the platform's unit economics live.

1. Clarify the requirements

Functional requirements

Non-functional requirements

State your assumptions out loud: one city region at a time, delivery only (no pickup), and courier locations refreshed every few seconds.

2. The order lifecycle as a state machine

The single most useful thing you can put on the whiteboard early is the order state machine. It makes the whole system legible, and it is where most failure handling lives.

  CREATED -> PAYMENT_AUTHORIZED -> SENT_TO_MERCHANT
                                        |
                        +---------------+---------------+
                        v                               v
                   CONFIRMED                        REJECTED -> REFUNDED
                        |
                        v
                    PREPARING  ---(courier assigned in parallel)
                        |
                        v
                  READY_FOR_PICKUP
                        |
                        v
                   PICKED_UP  ->  EN_ROUTE  ->  DELIVERED -> CAPTURED
                        |
                        +--> CANCELLED (policy: who pays?) -> REFUNDED
The order state machine — the backbone of a food delivery platform.

Model this as a persisted state machine with explicit allowed transitions, not as a pile of boolean columns. Every transition is written to an append-only event log, which gives you an audit trail for disputes, a stream for the tracking service, and replayability. Transitions must be idempotent: merchant tablets on flaky restaurant Wi-Fi will retry, and a duplicate "confirm" must not create a second order. Use an order ID plus a client-supplied idempotency key on every write.

Cancellation deserves a dedicated answer because it is genuinely three-sided: a cancel before CONFIRMED is cheap, a cancel after PREPARING means someone eats the cost of the food, and a cancel after PICKED_UP mostly becomes a refund decision. Say explicitly that policy, not the state machine, decides who pays.

3. Menus, availability, and restaurant search

Menus are read-heavy and write-light, so they cache well — but availability is the opposite. A merchant marking an item sold out, or pausing orders because the kitchen is swamped, must propagate in seconds, or customers order food that will never arrive. Split the two: version the menu document and serve it from a CDN or distributed cache, while keeping a small, fast-changing availability record that the storefront checks at render and again at checkout.

Discovery is a geospatial query: given the customer's location, find open merchants that deliver to that address. Index merchant coordinates with a geohash, quadtree, or S2 cell scheme so proximity becomes a prefix or range lookup rather than a full scan. Then filter — open now, address inside the delivery zone, items in stock — and rank by relevance, rating, and estimated delivery time. Note that the ranked ETA shown in the list is a cheaper, approximate version of the ETA you will commit to at checkout.

4. High-level architecture

  [Customer app]   [Merchant tablet]   [Courier app]
        |                 |                  |
        +--------- API gateway / auth --------+
                          |
   +--------+-------------+-------------+-----------+
   |        |             |             |           |
 Search   Order        Merchant      Dispatch    Location
 service  service      service       service     ingest
   |        |             |             |           |
 Geo      Order        Menu +        Assignment   Courier
 index    store +      availability  + batching   positions
          event log       |             |         (in-memory
   |        |             |             |          geo store)
   +--------+------+------+------+------+-----------+
                   |             |
              [ Event bus ]  [ ETA / ML service ]
                   |             ^
        +----------+----------+  | features
        v          v          v  |
   Tracking    Payments    Notifications   <- feature store
   (live)      + refunds   (push/SMS)
Three clients, one event bus: order service owns truth, dispatch and ETA consume it.

The order service owns the source of truth and emits events; everything else subscribes. Location ingest is deliberately separate from the order path — it is a firehose of small, lossy-tolerant writes that belongs in an in-memory geospatial store, not in your transactional database.

5. Prep-time estimation

Prep time is the variable that makes this problem distinctive, and it is a prediction, not a lookup. Useful signals include the merchant's historical time-to-ready, the item mix and order size, current kitchen load (how many orders that merchant has in flight), time of day and day of week, and whether the merchant confirms orders promptly. Merchants who use a tablet that reports "food ready" give you a ground-truth label; merchants who do not force you to infer readiness from courier pickup timestamps, which is noisier.

Be candid about the uncertainty. Prep time has a long tail, and the cost of error is asymmetric on both sides: too early and the courier idles, too late and the food cools. Most designs therefore work with a predicted distribution and pick an operating point rather than treating a point estimate as truth.

6. Dispatch: assignment and batching

Dispatch is the heart of the answer. The naive approach — assign the nearest available courier the instant the order is confirmed — fails for the reason above. Instead, run dispatch as a repeated batch optimisation: every few seconds, take the set of pending orders and the set of available couriers in a region and solve an assignment problem over that window, rather than greedily matching one order at a time. Delaying assignment slightly gives the optimiser more supply and more orders to work with, which is why "wait a bit before assigning" is a feature, not a bug.

The cost function is multi-objective. Typical terms: courier idle time at the merchant, food-wait time after ready, total delivery duration versus the promised ETA, courier travel distance (their earnings and your incentive spend), and fairness across couriers. Constraints include vehicle type, order size, and any age- or licence-restricted items.

Batching is the lever that changes unit economics. If two orders have pickups and drop-offs that are close together in space and time, one courier can carry both, raising deliveries per courier-hour. The dispatcher enumerates candidate routes over the combined pickup and drop-off points and accepts a batch only if the extra detour stays inside a per-customer delay budget. Cap batch size — every additional stop compounds risk against an ETA you already promised — and prefer batching two orders from the same merchant, which costs almost nothing extra.

Dispatch choiceWinsCosts
Greedy nearest courierSimple, instantIdle couriers, poor global utilisation
Windowed batch optimisationBetter global assignmentAdded latency, more complex
Order batching (multi-drop)Higher deliveries per hourETA risk for the first customer

7. Live tracking

Courier apps emit location every few seconds. Ingest through a lightweight endpoint into a partitioned in-memory geospatial store keyed by region, keep only the recent trail, and archive the rest asynchronously for analytics. Fan out to the customer's map over a persistent connection (WebSocket or server-sent events) with polling as the fallback, and smooth the path by snapping points to the road network so the marker does not jitter. Backpressure matters: at peak you would rather drop an intermediate position update than delay the whole stream.

8. ETA modelling

The customer sees one number, but it is a composition:

ETA = merchant_confirm + prep_time
    + courier_travel_to_merchant
    + wait_and_pickup_at_merchant
    + travel_to_customer
    + dropoff (parking, elevator, handoff)

Each leg is predicted separately, then combined — and because the legs overlap (the courier travels while the food cooks), the pickup moment is roughly the max of "food ready" and "courier arrives", not the sum. Quote a range rather than a single minute, and add a buffer: lateness hurts far more than an early arrival. Re-estimate continuously as the order progresses and push updates, because a silently wrong ETA generates support contacts, which are expensive.

9. Pricing, payments, and refunds

Pricing has several components: item prices set by the merchant, a delivery fee that varies with distance and current supply and demand, service fees, and courier incentives during undersupply. At a high level, surge-style dynamic pricing exists to balance a marketplace — when demand outruns available couriers, raising fees and courier pay pulls supply onto the road and shifts some demand later. Say that, and say that you would cap and clearly disclose it; do not pretend to know any company's exact formula.

Payments follow authorise at checkout, capture on delivery, which handles the common case where the final total shifts (a substituted item, an added tip). Treat the payment flow as a saga across order, payment, and merchant payout services with compensating actions rather than a distributed transaction, and make every step idempotent against a payment intent ID. Refunds are their own workflow: partial refunds for missing items, full refunds for undelivered orders, with automated approval under a threshold and manual review above it to limit abuse.

10. Scale and trade-offs

Framework reminder: The arc is the same as every design answer — requirements → core entity and lifecycle (the order state machine) → the hard subproblem (prep-time-aware dispatch and batching) → supporting services → trade-offs. The pieces recur elsewhere: geospatial indexing and live tracking in Design Uber, and ranking and prediction in Design a Recommendation System.

Practice distributed systems with live AI support

CoPilot Interview surfaces a structured design skeleton — requirements, entities, the core algorithm, and the trade-offs worth naming — in about 4 seconds during real Zoom and Teams calls. Free tier for Windows and macOS. Explore the free AI interview assistant.

Download free

FAQ

How is designing DoorDash different from designing Uber?

Ride-hailing is a two-sided marketplace that matches a rider to a nearby driver, and the trip starts as soon as the driver arrives. Food delivery is a three-sided marketplace involving the customer, the merchant, and the courier, and it adds a production step: the restaurant has to cook the food. That single difference drives most of the design, because dispatch must time the courier's arrival to the predicted prep completion, the ETA is a sum of prep, pickup and travel legs, and one courier can carry several orders at once.

Why does courier assignment depend on food prep time and not just proximity?

Assigning the closest courier immediately makes them wait idle at the restaurant until the food is ready, which wastes supply during peak hours. Assigning too late leaves the food sitting on the pass getting cold. The dispatcher therefore predicts when the order will be ready and picks a courier whose estimated arrival at the merchant lands close to that moment, optimising a cost function over wait time, food freshness, and total delivery duration rather than distance alone.

How does batching multiple orders to one courier work?

Batching assigns two or more orders to a single courier when their pickups and drop-offs are close enough that the combined route costs little extra time. The dispatcher searches candidate routes over the pickup and drop-off points, and accepts a batch only when the added detour stays inside a delay budget for each customer. Batching raises deliveries per courier-hour and lowers cost per order, but it must be capped because every extra stop adds risk to the promised ETA.

How is the delivery ETA calculated?

The quoted ETA is a composition of several predicted legs: time for the merchant to confirm and prepare the order, courier travel time to the restaurant, wait and pickup time at the merchant, travel time to the customer, and drop-off time including parking and building access. Each leg is a separate prediction fed by historical data for that merchant, item mix, time of day, and traffic. Platforms usually quote a range and add a buffer, because customers dislike a late delivery far more than an early one.

How do you find nearby restaurants efficiently?

Restaurant discovery is a geospatial query: given the customer's location, return open merchants that deliver to that address. Encoding coordinates with a geohash, quadtree, or S2 cell index turns the two-dimensional proximity problem into a prefix or range lookup that a database can serve quickly. Results are then filtered by whether the merchant is currently open and staffed, whether the address falls inside its delivery zone, and whether items are in stock, before ranking by relevance, rating, and estimated delivery time.