Skip to content
System design course
Ch.4 · Designing real systems·concept ·8 min read

Designing DoorDash (food delivery)

A three-sided marketplace — customers, restaurants, and couriers — with order orchestration, courier matching, and delivery batching on top of geospatial matching.


The problem

Design DoorDash/Uber Eats: a customer orders from a restaurant; the system sends the order to the restaurant and dispatches a courier to pick it up and deliver it. It’s a three-sided marketplace (customers, merchants, couriers) — extending Uber’s matching with an order-orchestration workflow and delivery batching.

Step 1 — Requirements

Functional: browse nearby restaurants + menus; place an order; restaurant accepts/prepares; match a courier for pickup+dropoff; live tracking; ETAs; payment.

Non-functional: low-latency browse/matching, reliability of the order workflow (don’t lose/duplicate orders), high write volume (locations), available; order/payment state must be consistent.

Step 2 — Discovery (geospatial, reuse Yelp)

Nearby restaurant search reuses the geohash/quadtree geo index (Yelp): index restaurants by cell, query nearby cells, filter (cuisine, open, delivery range), rank by distance/rating/prep-time. Menus and restaurant details are cached (mostly static).

Step 3 — The order orchestration workflow (the new core)

An order coordinates three parties through a multi-step saga (a distributed workflow with compensations), because there’s no single transaction across customer payment, restaurant acceptance, and courier dispatch:

place order → authorize payment → notify restaurant → restaurant accepts
   → dispatch courier (timed to prep) → courier picks up → delivers → capture payment
   (any step fails → compensate: refund, cancel, reassign)

A saga / state machine drives this with idempotent steps and compensating actions (refund if the restaurant rejects, re-dispatch if a courier drops). This reliability workflow is what distinguishes the problem.

Step 4 — Courier matching (reuse Uber, with timing)

Matching couriers reuses Uber’s in-memory geo index of active couriers + lease-based dispatch, but with a twist: dispatch is timed to food readiness — assign a courier to arrive about when the food is ready (not too early to wait, not too late so it’s cold). Estimate prep time + travel time and dispatch accordingly.

Step 5 — Delivery batching (efficiency)

A courier can carry multiple orders going the same direction. Batch nearby orders with compatible timing/route to raise courier efficiency — an optimization (route + time-window constrained) that lowers cost and delivery price. Mention it as the key efficiency lever.

Step 6 — Architecture

browse → geo index (restaurants) + menu cache
order → order/saga service (state machine) ⇄ payment, restaurant, courier services
courier match → in-memory geo index (active couriers) + lease dispatch (timed to prep)
tracking → location stream + pub/sub to customer/restaurant

Trade-offs to raise

  • Saga (no global transaction, eventual + compensations) vs trying for one transaction across three parties — saga is the realistic answer.
  • Dispatch timing — early (courier waits) vs late (food cold); estimate prep+travel.
  • Batching (cheaper, slightly slower per order) vs single-order (faster, costlier).
  • Consistency split — discovery/matching AP-ish; order/payment CP.

The interview cue

“Restaurant discovery reuses the geo index (Yelp); the order is a saga/state machine coordinating payment + restaurant + courier with idempotent steps and compensations; courier matching reuses Uber’s in-memory geo index + lease dispatch, timed to food readiness; batch nearby deliveries for efficiency.” Three-sided orchestration (saga) + timed courier matching is the distinguishing answer; implementation next.