~learn.

Swiggy Food Delivery, the whole design.

A three-sided marketplace that gets food from kitchens it doesn't run to customers across the city, about 2 million times a day, by dispatching riders just in time for the predicted ready time and promising a calibrated quantile, not an average.

6 diagrams · 8 sections · revise in ~10 min · Download PDF prints it in the light theme, diagrams included

SQ-17 · Swiggy Food Delivery · design sheethimanshukukreja.in/learn/sheets/bonus-17-swiggy-food-delivery

At a glance#

Number Value What it drives
Food customers · restaurants · riders 19.2M · 2.66 lakh · 6.48 lakh a month three partners who can each say no
Food orders a day (design target) 2.5 million ~2.1 million today from public figures
Orders at dinner peak · New Year's Eve ~83 / s · ~200 / s biryani peaked ~7× an average minute
Riders online at dinner ~120,000 ~27-minute trips, ~2.5 orders an hour
Location pings ~30,000 / s latest position in memory, ~8 MB
Pairs scored per city round ~10,000 every 20 s joint assignment, under a 1-minute target
Median order-to-door, ~3.5 km ~32 min the ride to the kitchen hides inside cooking
Promise target ≥ 90% kept per zone per hour quantile calibrated per zone

Architecture#

DiagramFood delivery on one page
%% title: Food delivery on one page
flowchart TD
  CA@{ icon: "sd:phone", label: "Customer app" } ==> GW@{ icon: "sd:gateway", label: "API gateway<br/>auth · rate limits" }
  GW --> SV["Serviceability + menus<br/>restaurants that can deliver now · ETA"]:::service
  GW ==> ORD["Order saga<br/>idempotency key · outbox · refunds<br/>orders DB sharded by city"]:::core
  ORD --> PAY@{ icon: "sd:card", label: "Payment gateways" }
  ORD ==> REST["Restaurant gateway<br/>accept ≤ 3 min · item issues · ready"]:::service
  REST --> RT@{ icon: "sd:tablet", label: "Restaurant tablet / POS" }
  ORD -.-> BUS@{ icon: "sd:stream", label: "Event bus<br/>order + rider events" }
  BUS ==>|"due for a rider"| ASG["Assignment per city<br/>20-s rounds · batching"]:::hot
  PRED@{ icon: "sd:ml", label: "Prediction<br/>prep · travel · ETA distribution" } --> ASG
  PRED --> SV
  LOC@{ icon: "sd:map-pin", label: "Location service<br/>latest position per rider" } --> ASG
  ASG ==>|"offers"| RA@{ icon: "sd:delivery", label: "Rider app<br/>ping every 4 s" }
  RA -.-> LOC
  class CA,RT,RA client
  class GW edge
  class PAY external
  class BUS queue
  class PRED service
  class LOC cache
DiagramOne assignment round in one city
%% title: One assignment round in one city
flowchart TD
  T@{ icon: "sd:timer", label: "Every 20 s, per city" } --> DUE@{ icon: "sd:queue", label: "Orders due<br/>ready − ride − buffer ≤ now" }
  T --> RID@{ icon: "sd:map-pin", label: "Candidate riders<br/>free or finishing · ≤ 3 km" }
  DUE --> PAIR["Pairs + two-order batches<br/>close pickups · same direction"]:::service
  RID --> PAIR
  PAIR --> SC["Score in minutes<br/>rider idle · food wait · lateness · fairness"]:::core
  SC --> SOLVE["Solve the whole round<br/>not greedy"]:::hot
  SOLVE --> OFF@{ icon: "sd:bell", label: "Offers · 20 s to accept" }
  OFF -.->|"declined"| DUE
  class T,RID cache
  class DUE queue
  class OFF client

Key flows#

DiagramOne order, tap to door
%% title: One order, tap to door
sequenceDiagram
  autonumber
  participant C as Customer
  participant O as Order saga
  participant R as Restaurant
  participant A as Assignment
  participant D as Rider
  C->>O: Pay (key k1) · ETA 34 min
  O->>R: Order on the tablet
  R-->>O: 0:50 · accepted, ready in 15 min
  Note over O,A: planned ready 15:50 · ride to kitchen ~6 min · dispatch due ~8:20
  O-)A: Due for a rider (via outbox)
  A->>D: 8:25 · offer
  D-->>A: Accepted
  D->>R: 15:05 · at the kitchen
  R-->>D: 16:40 · food handed over
  D-->>C: live location every few seconds
  D-->>O: 30:10 · delivered
DiagramThe kitchen doesn't answer
%% title: The kitchen doesn't answer
sequenceDiagram
  autonumber
  participant O as Order saga
  participant R as Restaurant
  participant P as Payments
  participant C as Customer
  O->>R: New order · timer starts
  Note over O: 90 s · no answer
  O->>R: Automated phone call
  alt accepted in time
    R-->>O: Accepted · prep time
  else 3 min, still nothing
    O->>P: Full refund (keyed on the order)
    O-->>C: Cancelled · refund on its way
    R-->>O: Late accept
    O-->>R: Already cancelled · don't cook
  end

Data model#

DiagramCore entities
%% title: Core entities
classDiagram
  class Order {
    +orderId : string
    +idemKey : string
    +cityId : string · shard key
    +state : OrderState
    +plannedReadyAt : timestamp
    +shownEtaMin : int
  }
  class Restaurant {
    +restaurantId : string
    +zone : string
    +medianOverrunMin : float
    +pausedUntil : timestamp
  }
  class Rider {
    +riderId : string
    +status : FREE / ON_TRIP / SILENT
    +cell : string
    +lastPingAt : epoch ms
  }
  class Offer {
    +offerId : string
    +orders : one or two order ids
    +expiresIn : 20 s
  }
  class Ping {
    +riderId : string
    +lat : float
    +lng : float
    +ts : epoch ms
  }
  Order "many" --> "1" Restaurant : cooked by
  Offer "1" --> "many" Order : carries
  Offer "many" --> "1" Rider : offered to
  Rider "1" --> "many" Ping : sends

Lifecycle#

DiagramOrder state machine
%% title: Order state machine
stateDiagram-v2
  [*] --> PLACED
  PLACED --> ACCEPTED: kitchen accepts
  PLACED --> CANCELLED: rejected or 3 min silence
  ACCEPTED --> ITEM_ISSUE: item out of stock
  ITEM_ISSUE --> ACCEPTED: replace or remove item
  ITEM_ISSUE --> CANCELLED: customer cancels
  ACCEPTED --> RIDER_ASSIGNED: dispatch time reached
  RIDER_ASSIGNED --> PICKED_UP: food handed over
  PICKED_UP --> DELIVERED
  PICKED_UP --> UNDELIVERED: customer unreachable
  DELIVERED --> [*]
  CANCELLED --> [*]
  UNDELIVERED --> [*]
  class DELIVERED ok
  class CANCELLED,UNDELIVERED bad
  class ITEM_ISSUE queue

Decisions#

Decision Chose Over Because
When to dispatch Ready time − ride − buffer The moment it's accepted riders don't wait 9 minutes at counters
How to match Joint solve per city round Greedy nearest rider first order can't steal the second's rider
Batching Only if both promises hold Whenever pickups are close nobody's food waits for the other order
Ready time Predicted from stated + history + queue Restaurant's number alone kitchens run late in patterns
Shown ETA Calibrated quantile per zone and hour Mean + one city-wide buffer each zone keeps ~90%
Kitchen steps Clock on every step, saga state wins Waiting for the restaurant no order left hanging
Locations Newest ping wins, cell index Store and scan every ping old pings can't move riders back

When it breaks#

Failure What users see Contain it with
Kitchen never accepts cancelled with a refund in 3 min timers, automated call, auto refund
Many kitchens can't cook fewer restaurants listed pause on rising cancellations
Rain at dinner longer, honest ETAs, smaller radius quantiles up, shrink radius, batch more
Rider goes silent mid-trip last known position, then an update 60-s silence = unknown, a person decides
Far order declined repeatedly revised ETA, else cancel + refund re-offer with higher priority and pay

Say it in the interview#