0) Problem Restatement
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.
Asked at: Flipkart, Meta, Uber — 4 candidate reports between Oct 2025 and Jun 2026.1) Requirements
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=andGET /v1/merchants/{id}/menuPOST /v1/orders(Idempotency-Key){ merchant_id, items, address, payment_method }POST /v1/orders/{id}/accept(merchant),POST /v1/orders/{id}/readyPOST /v1/couriers/{id}/location{ lat, lng, ts }GET /v1/orders/{id}/track(WebSocket or SSE for live updates)
2) High-Level Architecture
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
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 --> CU3) Data 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.
4) Key Flows
4.1 Placing an order
- Checkout validates the cart (prices, items available, merchant open).
- For quick commerce, reserve stock:
reserved_qty += nonly ifavailable_qty - reserved_qty >= n, in one atomic update. - Authorize payment, create the order (
placed) and publish an event. - 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
- When the order is accepted, dispatch starts so the courier arrives about when the food is ready (prep time estimate − travel time).
- 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.
- 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.
5) Deep Dive A — Dispatch and batching
- Greedy vs batch matching: instead of assigning each order the moment it's ready, collect orders and couriers every ~10–30 seconds in each area and solve a small assignment problem that minimizes total delay. That's better overall than first-come-first-served.
- Stacking: one courier can carry 2 orders from the same restaurant or nearby ones if it doesn't delay the first order too much.
- City sharding: dispatch works per city or zone, since orders never cross cities. This splits the load naturally.
6) Deep Dive B — Quick commerce inventory and failures
- 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.
7) Trade-offs & Alternatives
| Decision | Choice | Why | Alternative |
|---|---|---|---|
| Order consistency | Strongly consistent order DB + state machine | Money and fulfillment must be exact | Eventual NoSQL: risk of double assignment |
| Courier locations | In-memory geo index from a stream | Updates every 4s, instant queries | Write every GPS point to a DB: too slow and costly |
| Matching | Batched assignment per zone | Better global efficiency | Greedy nearest: simple, worse at peaks |
| Integration | Events via Kafka | Loose coupling, replayable | Direct calls everywhere: fragile chains |
8) Common Follow-up Questions
- "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.
9) Wrap-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.