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.