•CASE STUDY

Food and Quick-Commerce Delivery Platform (Uber Eats / DoorDash)

7 min read·1,257 words·Advanced

Asked at

4 candidate reports between Oct 2025 and Jun 2026

How to use this case study

SDE-2 / Mid

  • Explain the main actors (customer, merchant, courier)
  • The order state machine
  • The flow from placing an order to delivery

SDE-3 / Senior

  • Go deeper on courier matching and dispatch
  • Live location tracking
  • ETA estimates
  • Inventory accuracy for 10-minute grocery delivery

Staff / Principal

  • Discuss peak dinner-time load
  • City-level sharding
  • Batching orders per courier
  • Failure handling (merchant cancels, courier drops) and marketplace balance

Problem RestatementProblem

Design a delivery marketplace like Uber Eats, DoorDash, or a 10-minute grocery app like Flipkart Minutes. Customers browse nearby restaurants or a nearby dark store (a small warehouse only for delivery), add items to a cart, and pay. Merchants accept and prepare the order. Couriers are assigned to pick it up and deliver it. Everyone tracks the order live on a map.

This has three sides that must stay in sync, plus real-time location, so it combines search, orders, payments and dispatch.

RequirementsRequirements

1.1 Functional

  • Browse and search nearby merchants and menus (or dark-store inventory).
  • Cart, checkout and payment.
  • The merchant accepts or rejects and marks the order ready.
  • Assign a courier, then track pickup and delivery live.
  • ETAs and notifications at every step.

1.2 Non-Functional

  • Order correctness: no lost or double orders, and a correct state everywhere.
  • Low latency for browsing and for location updates (a few seconds).
  • Peak handling: dinner time is 3–5x the average.
  • Inventory accuracy for quick commerce (don't sell items that are out of stock).

1.3 Scale Estimates

  • 10M orders/day, peaking at 500 orders/sec at dinner.
  • 500K active couriers sending GPS every 4 seconds → 125K location updates/sec.
  • Browsing: 100M menu or listing views/day.

1.4 API Design

GET/v1/merchants?lat=&lng=&q=and GET /v1/merchants/{id}/menu
POST/v1/orders(Idempotency-Key) { merchant_id, items, address, payment_method }
POST/v1/orders/{id}/accept(merchant), POST /v1/orders/{id}/ready
POST/v1/couriers/{id}/location{ lat, lng, ts }
GET/v1/orders/{id}/track(WebSocket or SSE for live updates)

High-Level ArchitectureArchitecture

2.1 Overview

  • Discovery/Search: finds merchants near the address (geo index) and serves menus from a cache.
  • Cart & Order Service: builds the order, prices it, reserves inventory (quick commerce) and runs the order state machine.
  • Payment Service: authorizes at checkout and captures on delivery.
  • Dispatch Service: picks the best courier using live locations and ETAs.
  • Location Service: ingests courier GPS into an in-memory geo index and a stream.
  • Tracking/Notification Service: pushes status and courier location to the customer.

2.2 Architecture Diagram

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    CU["Customer app"] --> SR["Discovery / Search"]
    CU --> OS["Order Service - state machine"]
    OS --> PAY["Payment Service"]
    OS --> INV[("Inventory - dark stores")]
    OS --> DB[("Orders DB")]
    OS --> K[("Order events - Kafka")]
    K --> DSP["Dispatch Service"]
    CO["Courier app"] -->|"GPS every 4s"| LOC["Location Service"]
    LOC --> GEO[("In-memory geo index")]
    DSP --> GEO
    DSP -->|"offer job"| CO
    ME["Merchant tablet"] --> OS
    K --> TR["Tracking + Notifications"]
    TR --> CU

Data ModelData model

orders:        order_id, customer_id, merchant_id, courier_id, items (JSON), total_cents,
               status, address, created_at, eta
order_events:  order_id, status, ts, actor         -- full history
couriers:      courier_id, status (offline/available/on_job), vehicle, current_order_id
inventory:     store_id, sku, available_qty, reserved_qty   -- quick commerce
Order states: placed → accepted → preparing → ready_for_pickup → picked_up → delivered, with side paths cancelled and rejected.

Key FlowsFlows

4.1 Placing an order

  1. Checkout validates the cart (prices, items available, merchant open).
  2. For quick commerce, reserve stock: reserved_qty += n only if available_qty - reserved_qty >= n, in one atomic update.
  3. Authorize payment, create the order (placed) and publish an event.
  4. The merchant tablet gets the order and accepts it. If it doesn't accept within ~3 minutes, the order is cancelled and refunded automatically.

4.2 Dispatching a courier

  1. When the order is accepted, dispatch starts so the courier arrives about when the food is ready (prep time estimate − travel time).
  2. Find available couriers near the merchant from the geo index, score them (ETA to merchant, current load, ratings), and offer the job to the best one with a 30-second timeout. If declined, offer it to the next.
  3. Assignment uses a conditional update (set courier_id only if still null) so an order never gets two couriers.

4.3 Tracking

Courier GPS → Location Service → a stream per active order → pushed to the customer's app every few seconds with an updated ETA.

Deep Dive A — Deciding which courier takes which orderDeep dive

Orders become ready continuously and couriers free up continuously. The matching policy is the single biggest lever on delivery time and courier earnings.

Weak

Give each order to the nearest free courier immediately

The moment an order is ready, find the closest available courier and assign it.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  O1["Order A ready - 12:00:01"] --> N1["Nearest courier: Sam, 200 m"]
  N1 --> ASSIGN["Sam assigned to A"]
  O2["Order B ready - 12:00:04, same block"] --> N2["Sam is gone"]
  N2 --> FAR["Nearest now: Rhea, 2.1 km"]
  FAR --> LATE["Order B delivered 14 minutes late"]

Each decision is locally optimal and the sequence is not. Greedy assignment spends the nearby couriers on whichever order happened to be ready first, and the order three seconds behind it — from the same restaurant — gets someone across the city. Nobody is choosing that; it falls out of deciding one order at a time.

Good

Wait a few seconds and solve a batch

Collect orders and available couriers over a 10–30 second window per zone, then solve a small assignment problem that minimises total lateness across the whole set.

The extra few seconds buy far more than they cost, because the solver can see that Sam should take B and Rhea should take A. This is the core of the answer. What it still does not use is the fact that one courier can carry more than one order.

Best

Batch, then stack, inside a zone

Add stacking to the batch solve: let one courier carry two orders when the pickup points are close and the detour does not push the first order past its promise.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  W["Every 10-30 s, per zone"] --> POOL["Ready orders + free couriers"]
  POOL --> SOLVE["Assignment solve - minimise total lateness"]
  SOLVE --> STACK{"Two orders, same or nearby pickup?"}
  STACK -->|"detour under the cap"| PAIR["Stack on one courier"]
  STACK -->|"over the cap"| SEP["Separate couriers"]
  PAIR --> DISP["Dispatch"]
  SEP --> DISP

Stacking is where the economics live — a courier delivering two orders on one trip roughly halves the cost per delivery — and it is also the easiest thing to get wrong. Cap it by added delay to the first order, not by distance: a second pickup 100 metres away at a restaurant with a 12-minute queue is a worse stack than one 800 metres away that is ready now.

Shard dispatch by city or zone. Orders never cross cities, so this partitions the problem for free and keeps each assignment solve small enough to run in well under the batching window.

Deep Dive B — Quick commerce inventory and failuresDeep dive

  • Stock is counted per dark store. Reservations expire if payment fails (e.g., after 10 minutes), which releases stock.
  • Pickers scan items when packing. If something is missing, the customer gets an instant substitution or partial refund.
  • Failures: the merchant rejects → refund. The courier cancels → re-dispatch at higher priority. The app crashes mid-checkout → the idempotency key prevents double orders. Every state change is an event, so any service can rebuild what happened.

Trade-offs & AlternativesTrade-offs

DecisionChoiceWhyAlternative
Order consistencyStrongly consistent order DB + state machineMoney and fulfillment must be exactEventual NoSQL: risk of double assignment
Courier locationsIn-memory geo index from a streamUpdates every 4s, instant queriesWrite every GPS point to a DB: too slow and costly
MatchingBatched assignment per zoneBetter global efficiencyGreedy nearest: simple, worse at peaks
IntegrationEvents via KafkaLoose coupling, replayableDirect calls everywhere: fragile chains

Common Follow-up QuestionsFollow-ups

  • "How do you compute ETAs?" An ML model trained on past trips (distance, traffic, time of day, restaurant prep history), updated live during delivery.
  • "Dinner peak?" Autoscale stateless services, pre-scale before known peaks, and use surge incentives to bring more couriers online.
  • "How do you cache menus?" Menus change rarely. Cache them per merchant with invalidation on edit, and serve them via CDN.

Wrap-UpWrap-up

Use a strict order state machine in a consistent DB, emit events for every change, and let separate services handle payment, dispatch, tracking and notifications. Keep courier locations in an in-memory geo index fed by a stream, match couriers in small batches per zone with a conditional assignment, and for quick commerce reserve inventory atomically with expiring holds.

More Case Studies

Frequently Asked Questions

What is the Food and Quick-Commerce Delivery Platform (Uber Eats / DoorDash) system design question?

Food and Quick-Commerce Delivery Platform (Uber Eats / DoorDash) is a system design interview question asked at FAANG companies. It covers geospatial, real-time, e-commerce, distributed systems and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Food and Quick-Commerce Delivery Platform (Uber Eats / DoorDash) question?

Flipkart, Meta, Uber have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Food and Quick-Commerce Delivery Platform (Uber Eats / DoorDash) interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Food and Quick-Commerce Delivery Platform (Uber Eats / DoorDash) question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →