~learn.

Uber, the whole design.

A ride-hailing marketplace that matches a rider to a driver in under 3 seconds while ~500K driver pings a second land at peak, by keeping live locations as soft state in an H3 hexagon index and only trips in a strongly consistent store.

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

SQ-09 · Uber · design sheethimanshukukreja.in/learn/sheets/bonus-09-uber

At a glance#

Number Value What it drives
Ride trips 30M a day (design assumption) ~1,000 requests / s at a 3× peak
Location pings ~375K / s, ~500K at peak 1.5M online drivers × one ping every 4 s
Request → driver notified < 3 s ring search, ETA for a few dozen candidates
Driver offer window 15 s then the next best driver
H3 cell res 9 · ~0.1 km² · edge ~200 m street-block buckets, rings k = 1, 2…
Storage 150 GB / day trips · 4.3 TB / day pings ~55 TB a year of trips; pings kept 7 days ≈ 30 TB
Surge per cell · every 30 s · cap 5× smoothed with an EMA, α = 0.3

Architecture#

DiagramUber on one page: soft-state locations, strongly consistent trips
%% title: Uber on one page: soft-state locations, strongly consistent trips
flowchart TD
  RA@{ icon: "sd:phone", label: "Rider app<br/>WebSocket + HTTP" } --> GW@{ icon: "sd:gateway", label: "API gateway<br/>auth · rate limit · routing" }
  DA@{ icon: "sd:car", label: "Driver app<br/>ping every 4 s" } --> GW
  GW --> LS["Location service<br/>~500K pings / s · Kalman filter"]:::core
  GW ==> MS["Matching / dispatch<br/>ring search · ETA score · 15 s offers"]:::hot
  GW --> TS["Trip service<br/>exactly-once create · state machine"]:::core
  LS --> RD@{ icon: "tech:redis", label: "Redis · H3 cell → drivers<br/>driver hash, TTL 30 s" }
  MS --> RD
  TS --> SP@{ icon: "tech:spanner", label: "Spanner<br/>trips, strongly consistent" }
  LS -.->|"pings"| KF@{ icon: "tech:kafka", label: "Kafka<br/>pings + every state change" }
  TS -.->|"state changes"| KF
  KF --> CA@{ icon: "tech:cassandra", label: "Cassandra<br/>location history · 7 days" }
  KF --> FL@{ icon: "tech:flink", label: "Flink<br/>demand per cell" }
  FL --> PR["Pricing<br/>surge per cell · upfront fare locked"]:::service
  class RA,DA client
  class GW edge
  class RD cache
  class SP,CA store
  class KF queue
  class FL service
DiagramMatching: ring search, score, offer one driver at a time
%% title: Matching: ring search, score, offer one driver at a time
flowchart TD
  REQ["Ride request<br/>pickup → H3 cell, res 9"]:::service --> RING["Ring search · k = 1, 2… until enough<br/>available · ride type · not already pinged"]:::core
  RING --> SC["Score ≤ 50 candidates<br/>0.4 ETA · 0.2 rating · 0.2 acceptance · 0.2 wait"]:::service
  SC -.->|"rush hour"| BA["Batch 2 s of requests<br/>solve the assignment jointly"]:::service
  SC --> OF{"Offer to best · mark PENDING<br/>wait ≤ 15 s"}
  OF -->|"accept"| TR["Trip created once"]:::ok
  OF -->|"decline · timeout"| NX["Clear PENDING<br/>next best, up to 10"]:::queue
  OF -->|"none left"| NO["No drivers · all declined"]:::bad
  NX --> OF

Key flows#

DiagramRequest a ride: matched driver notified in under 3 s
%% title: Request a ride: matched driver notified in under 3 s
sequenceDiagram
  autonumber
  participant R as Rider app
  participant M as Matching
  participant L as Location index
  participant D as Driver app
  participant T as Trip service
  R->>M: Request · pickup · destination · ride type
  M->>L: Drivers in pickup cell + rings
  L-->>M: Candidates
  Note over M: filter · ETA each · score · pick best · mark PENDING
  M->>D: Offer over WebSocket
  Note over R,D: under 3 s from request to here
  D-->>M: Accept (within 15 s)
  M->>T: Create trip (request ID)
  Note over T: once per request · fare locked at quote
  T-->>R: Driver · ETA
  T-->>D: Pickup · route
  loop every 4 s
    D-)L: Location ping
    L-)R: Car moves on the map
  end
DiagramDriver goes silent mid-offer: time out, offer the next
%% title: Driver goes silent mid-offer: time out, offer the next
sequenceDiagram
  autonumber
  participant M as Matching
  participant D1 as Best driver
  participant D2 as Next driver
  participant L as Location index
  M->>D1: Offer · D1 marked PENDING
  Note over D1: phone loses signal
  Note over M: no answer in 15 s · treat as decline
  M->>M: Clear D1's PENDING
  M->>D2: Offer · D2 marked PENDING
  alt D2 accepts
    D2-->>M: Accept → trip created
  else D2 declines too
    Note over M: next best, up to 10 candidates
  end
  Note over L: no ping from D1 for 30 s · TTL drops D1 from search
DiagramRegion fails at rush hour: keep trips, rebuild locations
%% title: Region fails at rush hour: keep trips, rebuild locations
sequenceDiagram
  autonumber
  participant D as Driver app
  participant B as Healthy region
  participant T as Trip store
  participant L as Location index
  Note over D: region A down · app keeps the trip and GPS trail
  D->>B: Reconnect
  B->>T: Load trip (replicated active-active)
  T-->>B: Rider · driver · locked fare · status
  D->>L: Fresh ping (every 4 s)
  Note over L: soft state rebuilt in a ping cycle or two
  D->>B: Complete trip (trip ID)
  D->>B: Complete trip again (retry after timeout)
  alt same trip ID already completed
    B-->>D: Existing result · one charge only
  end

Data model#

DiagramCore entities
%% title: Core entities
classDiagram
  class Driver {
    +driverId : string
    +rideTypes : X / XL / Black
    +rating : float
    +acceptanceRate : float
    +status : online / pending / on trip
  }
  class DriverLocation {
    +driverId : string
    +lat : float
    +lng : float
    +h3Cell : res 9
    +ts : timestamp · TTL 30 s
  }
  class Trip {
    +tripId : string · idempotency key
    +requestId : string
    +riderId : string
    +driverId : string
    +state : TripState
    +upfrontFare : locked at request
  }
  class CellSurge {
    +cellId : string
    +multiplier : 1.0 to 5.0
    +demand : requests, last 5 min
    +supply : cell + 6 neighbours
  }
  class Rider {
    +riderId : string
    +paymentMethods : list
  }
  Driver "1" --> "1" DriverLocation : latest only
  Rider "1" --> "many" Trip : requests
  Driver "1" --> "many" Trip : drives
  Trip "many" --> "1" CellSurge : priced by pickup cell

Lifecycle#

DiagramTrip state machine
%% title: Trip state machine
stateDiagram-v2
  [*] --> REQUESTED
  REQUESTED --> MATCHING: fare quoted
  MATCHING --> OFFERED: best driver, PENDING
  OFFERED --> MATCHING: decline or 15 s timeout
  MATCHING --> NO_DRIVERS: none left
  OFFERED --> MATCHED: driver accepts
  MATCHED --> EN_ROUTE: driving to pickup
  EN_ROUTE --> ON_TRIP: rider picked up
  MATCHED --> CANCELLED: rider or driver cancels
  EN_ROUTE --> CANCELLED: rider or driver cancels
  ON_TRIP --> COMPLETED: drop-off
  COMPLETED --> PAYMENT_RETRY: card declined
  PAYMENT_RETRY --> PAID: backup method
  COMPLETED --> PAID: charged
  PAID --> [*]
  NO_DRIVERS --> [*]
  CANCELLED --> [*]
  class PAID ok
  class NO_DRIVERS,CANCELLED bad
  class OFFERED,PAYMENT_RETRY queue

Decisions#

Decision Chose Over Because
Live locations Redis, overwritten each ping A DB write per ping ~500K writes / s; a few seconds stale is fine
Spatial index H3 hexagons, res 9, ring search Squares or scan all drivers one neighbour distance; rings ≈ circles
Trip store Spanner, exactly-once create An eventual store a duplicate trip is a double charge or two cars
Ranking Weighted score led by ETA Nearest in a straight line rivers, one-way streets, driver fairness
Dispatch One offer at a time, PENDING flag, 15 s Broadcast to many drivers no double-dispatch of one driver
Rush hour 2 s batches, joint assignment Greedy one by one lower total pickup time
Surge Per cell, cell + 6 neighbours, EMA, cap 5× One city-wide price local, smooth, no sudden jumps

When it breaks#

Failure What users see Contain it with
Driver loses signal mid-offer a slightly longer wait 15 s timeout → next driver; TTL drops them at 30 s
Concert ends, 50,000 requests surge, a queue surge, virtual queue, pre-positioned drivers, predictive scaling
GPS drift car in the wrong cell Kalman filter, k-ring search, wider radius
Card declined after the trip nothing for the driver backup payment method, grace period, driver always paid
Region down at rush hour a reconnect blip trips replicated active-active, index rebuilt from pings, trip-ID idempotency
Location service slow bad matches, wrong ETAs critical alert at 500 ms; it sits on the live path

Say it in the interview#