Marketplace & Booking
Design DoorDash / Uber Eats
01
Requirements
Requirements
- Eater browses local restaurant menus; places an order with customizations
- Order routes to restaurant; kitchen accepts/rejects/adjusts prep time
- Dispatch engine picks a nearby Dasher and offers the order; Dasher accepts/declines
- Live order tracking map showing Dasher location + ETA updates
- Payment charged on order; tips added/modified post-delivery; refunds for issues
- Ratings + tips; dispute resolution; scheduled orders
- Order placement < 500 ms p99; live tracking < 2 s update latency
- Dispatch decision < 3 seconds — balance of quality vs. latency
- ETA accuracy within ~3 min on 90th percentile
- Strong consistency on orders + payments; eventual on search / browse
- 99.99% availability on order placement (revenue-critical)
- Scale to ~6M orders/day = ~70/sec avg, ~600/sec peak (Friday dinner)
02
Scale Estimation
Scale Estimation
03
API Design
API Design
Geo-indexed restaurant search. Returns {restaurants: [{id, name, cuisine, eta_range, fee, rating}], hot_tiles}. Served from a read replica with restaurants clustered by H3/geohash cell.
Create order with idempotency key. Body: {restaurant_id, items[], address, payment_token, tip_amount}. Returns {order_id, total, estimated_eta, status: 'pending_restaurant'}. Strongly consistent; wrapped in a transaction with payment auth.
Live order status + Dasher location. Eater clients poll this every 4 s (or WebSocket push). Returns current {state, dasher_location, eta, elapsed}.
Dasher app posts GPS ping every 4 s when active. Goes to the geo index + the live-tracking push bus. Fire-and-forget; retry on failure.
Dasher accepts/declines a dispatched offer. Response window is ~30 s; server auto-rejects and re-dispatches after timeout.
Restaurant POS integration or tablet app updates ready-by-time. Critical signal for dispatch timing.
Post-delivery tip modification. Hits payment service; updates order ledger. Idempotent.
04
Architecture
Architecture
Three primary domains: Marketplace (restaurants, menus, browse), Order (checkout, lifecycle, payments), and Logistics (dispatch, ETAs, live tracking). Each is an independent service with its own datastore. They communicate via events on Kafka.
05
Deep Dive — Dispatch Engine
Deep Dive — Dispatch Engine
Dispatch is the heart of the system. An order ready (or soon-to-be ready) needs matching to a Dasher with the best tradeoff across: pickup distance, estimated total trip time, driver earnings fairness, batching potential (combine with nearby orders), and restaurant prep synchronization. This is a combinatorial assignment problem at scale.
Naive approach: nearest Dasher wins. Good enough for small markets, terrible at scale — you'd leave Dashers idle on one side of the map while orders pile up on the other.
Modern dispatch algorithm (simplified):
- Candidate generation. Query H3 geo-index for Dashers within ~3 km of restaurant. Typically 10–50 candidates.
- Feature extraction. For each (Dasher, order) pair compute: pickup distance, Dasher current queue length, restaurant prep time, historical Dasher acceptance rate, current traffic on route.
- Score each pair with an ML model predicting "delivery quality" — likelihood of on-time + Dasher acceptance + CS-call-free outcome.
- Solve as assignment problem. For N open orders × M candidate Dashers, run the Hungarian algorithm (or approximation) to maximize total score subject to "each Dasher gets at most 1 order." Batch every ~2 s to give multiple orders a chance to co-optimize.
- Offer to winning Dasher. Send push notification; 30 s response window. If declined, re-run assignment with remaining candidates.
- Consider batching. Two orders at the same restaurant or two orders on the same route → offer both to one Dasher; cut cost per delivery ~30%.
sequenceDiagram
participant E as Eater
participant O as Order svc
participant M as Merchant
participant D as Dispatch
participant GEO as Location svc
participant DSR as Dasher
E->>O: POST /orders
O->>M: forward + prep-time request
M-->>O: accept + 12 min
O->>D: OrderReady @ T+10 min event
D->>GEO: nearby Dashers within 3 km
GEO-->>D: candidates [D1..D25]
Note over D: score candidatesML + Hungarian
D->>DSR: offer to best Dasher
alt accepted
DSR-->>D: accept
D->>O: AssignmentCommitted
O-->>E: ETA + Dasher info
else declined / timeout 30s
D->>D: remove + reassign
end
Why not run dispatch on every order immediately? Because the optimal assignment often looks 60–120 s into the future. Running a batched window lets "order A places at T, order B places at T+30 s near the same restaurant" be offered as a batch. Pure first-come-first-served leaves money on the table.
Location service. Every Dasher pings location every ~4 s. The service maintains a live H3 geo-index: H3 cell → set of Dasher IDs. Lookups are ring queries — "give me Dashers in this cell and its 18 neighbors." Stored in Redis for speed; bulk location history archived to Cassandra for ML training.
ETA. A separate ML service with features: distance, time-of-day traffic, historical restaurant prep time, Dasher speed profile, weather. Runs a GBDT model every time an eater loads the tracking page. P90 error target: ~3 minutes.
"Dispatch runs in ~2 s windows as a batched assignment problem. Query H3 geo-index for Dashers within 3 km, score each (order, Dasher) pair with an ML model on distance + prep time + historical signals, solve with Hungarian algorithm to maximize global score. Batch two nearby orders to one Dasher when possible. Offer sent via push with 30 s response window; on reject, re-dispatch. ETA served by a separate GBDT model refreshed on every tracking-page open. Location service uses H3 cells + ring queries, hot data in Redis, history in Cassandra."
06
Tradeoffs & Design Choices
Tradeoffs & Design Choices
- Batched dispatch vs instant. Batching (every 2 s) improves global efficiency but adds latency per order. Instant feels faster but is locally greedy and wasteful. The 2-second window is the sweet spot — imperceptible to eater, big marketplace-efficiency win.
- Order-as-saga, not a single transaction. Create order → auth payment → confirm with restaurant → dispatch Dasher — each step can fail. Saga pattern with compensating actions (auto-refund on restaurant decline, re-dispatch on Dasher timeout) beats trying to do it as one distributed transaction.
- ETA accuracy vs pessimism. A promised 25 min ETA delivered in 23 min delights the user; a promised 25 min delivered in 28 min makes them angry even though it's "close." ETAs are intentionally padded ~2 min for this UX asymmetry.
- H3 cells vs quadtree vs geohash. H3 (Uber's hex grid) beats square grids because hex neighbors are all equidistant, simplifying ring queries. Geohash has edge-of-cell artifacts. DoorDash adopted H3 (originally Uber's library) for dispatch lookups.
- Strong consistency where it matters. Order state transitions are sagas with idempotent steps in Postgres; payments are 2-phase (auth + capture); dispatch assignments are Cassandra-style writes with dedup. Browse/search can be eventually consistent (cached menus are fine for 60 s).
07
Failure Modes
Failure Modes
08
Anti-patterns
Anti-patterns
Greedy assignment misses batching opportunities; Dashers deadhead.
Millions of clients × 1/sec = DDoS on your own API.
Merchant / Dasher / Rider / Payments all fight over this hot row.
09
Interview Tips
Interview Tips
- Lead with dispatch, not with search. The marketplace is boring; dispatch is where the interesting engineering is. Interviewers expect you to pick dispatch as the deep-dive.
- Explicitly call out "three-sided." Eaters, merchants, Dashers each have their own app, their own SLA, their own failure modes. Many candidates forget the merchant surface entirely.
- Saga over distributed transactions. Don't try to "2PC across restaurant + payment + dispatch." Saga with idempotent compensations is the production pattern.
- H3 over geohash. Mention H3 by name — Uber popularized it, DoorDash uses it, Yelp uses it. Shows domain fluency.
- ETA is ML, not "nearest(restaurant, address)/avg_speed." Frame it as a model trained on millions of historical deliveries. Distance is one feature among many.
10
Evolution
Evolution
MVP — single city, one PostgreSQL, human dispatcher
Orders into Postgres; ops team manually calls Dashers. Works for ~1000 orders/day. DoorDash's first year-ish.
Rule-based auto-dispatch + Dasher app
Nearest-available with basic filters. Sharded DB by region. Carries to ~100K orders/day.
Batched assignment + H3 geo-index
Shift to 2-second dispatch windows with Hungarian-algorithm assignment. H3 cells for fast nearest-Dasher queries. ~1M orders/day.
ML-driven dispatch + ETAs
GBDT models on historical delivery data replace heuristics for both assignment scoring and ETA prediction. Acceptance rates up, CS contacts down.
Multi-order batching + vertical expansion
One Dasher carries multiple orders on overlapping routes; cost per delivery drops ~30%. Platform extends to groceries, convenience, returns.
Watch and read
References & Videos
Try next
Free to read · better with Enzo
Whiteboard this with Enzo
Enzo runs it as a live system design round on the whiteboard and grades your trade-offs.