SystemDesigndoc
Case Study

How Uber Works — System Design Deep Dive

Deep dive into Uber's architecture: real-time matching, geospatial indexing, surge pricing, ETA calculation, and how they handle millions of rides daily.

2026-03-23ubergeospatialreal-timemicroservicesdistributed-systems

Uber wordmark

Opening: Three Seconds That Move a Continent

You tap Request. Your phone sends a payload a few hundred bytes wide: who you are, where you are (with uncertainty), which product you want (UberX, Comfort, XL…), and a fingerprint of your session. Within roughly one to three seconds—often under two on a healthy network—you see a driver’s name, vehicle, and an ETA. Behind that mundane UX is one of the largest real-time control systems on Earth: geospatial indexing over millions of moving nodes, dispatch optimization under hard latency SLOs, routing and traffic fusion, dynamic pricing, fraud and risk, payments and settlement, and regional failover when dependencies misbehave.

This article is a case study in how Uber’s engineering organization turned that impossible problem into a domain-oriented microservice fabric with well-known open-source footprints (H3, Jaeger, contributions around Kafka, Pinot, Flink, MySQL scaling patterns, and more). Numbers below are order-of-magnitude anchors drawn from public talks, engineering blogs, and industry reporting—Uber’s internal figures move every quarter, but the shapes (what breaks first, what must never break) are stable.

The scale that constrains every design choice

SignalRepresentative magnitudeEngineering consequence
Completed trips & deliveries (platform-wide)Tens of millions per dayEvery completion emits multiple events; idempotency and ledger correctness dominate
Countries / cities70+ countries, 10,000+ citiesRegulatory, payment, and mapping heterogeneity is permanent
Mobile clientsHundreds of millions of installsLong tails of old OS versions, flaky networks, and aggressive battery optimization
Peak write throughput on core pipelinesMillions of events/sec class during spikesKafka-style logging and stream processors are not optional
Online driver/rider state changesContinuous GPS streamsSpatial indexes must update incrementally—not per-request table scans
Microservices (logical)Hundreds to 1000+ named services in large SOA mapsOwnership, blast-radius control, and DOMA-style boundaries matter more than “more services”
Data platform messagesTrillions of messages class over multi-year Kafka deployments (public talks)Multi-cluster, schema evolution, and replay are first-class

How to read the numbers

Treat every absolute as a moving baseline. The point is not “is it exactly 23.4M trips?” but “is your design still sane at 10× this load?” If not, revisit spatial partitioning, async boundaries, and degradation.

H3 hierarchical spatial indexing (Uber open source)

Hexagonal neighbor relationships in H3 (from official H3 site assets)

Microservices logical icon (IBM Carbon, Wikimedia Commons)

Apache Kafka logo (Apache/Wikimedia)

OpenStreetMap logo — road-graph culture starts here


1. The Scale of Uber — Numbers That Rewrite Intuition

Uber is not “an app.” It is a marketplace control plane layered on top of maps, payments, trust & safety, and local regulation. The product surface is simple; the failure surface is enormous.

1.1 Uber by the numbers (publicly anchored)

Metric bucketExample public anchorsWhy architects care
Trips / dayInvestor updates often cite ~20M+ trips/day platform-wide (Mobility + Delivery vary by quarter)Drives idempotency keys, outbox patterns, and reconciliation jobs
MAPC / riders~150M+ monthly active platform consumers (historical ranges in filings)Long-tail device fragmentation shapes API and push strategies
Drivers & couriers~6M+ quarterly active earners (historical)Supply churn and incentive systems are data-intensive
Cities10,000+Surge polygons, local rules, and map freshness differ per metro
Engineering headcountThousands of engineers across product infraDOMA ownership and internal platforms (routing, ML, data) amortize cost
MicroservicesPublic architecture discussions reference hundreds of critical path services; org charts exceed 1000 logical servicesRequires service mesh discipline, SLOs, and tiering
KafkaUber talks cite trillions of messages/year across data-plane KafkaMirrorMaker / multi-region / schema registry are baseline
Peak request ratesEdge + API tiers see very high RPS during events; core dispatch loops target sub-second decision cyclesTimeouts, hedging, and circuit breaking are universal

Throughput without correctness is liability

At this scale, duplicate events (at-least-once delivery) are normal. Your domain model must tolerate retries, replays, and split-brain windows unless you pay for strong cross-service transactions (you usually cannot).

1.2 End-to-end latency budget (why 3 seconds is a hard constraint)

The rider only experiences one timer, but the server stitches dozens of internal steps. A back-of-the-envelope for an on-demand match in a dense city might look like:

Phasep50 (ms)p99 (ms)Notes
TLS + API Gateway5–1540–80Includes auth token validation
demand-svc persistence10–2580–150Idempotent insert + outbox
geo-index lookup5–1550–120Hot buckets cached in memory
routing batch ETA30–120250–600Dominates when candidate set is large
scoring + fairness5–2040–100CPU-light vs routing
offer fan-out + push20–80150–400APNs/FCM tails are ugly
client animation10–3050–100Not server-side, but steals perceived SLO

Nothing here is “free.” If routing p99 slips by 200 ms, you consume the entire budget for reoffer rounds—so dispatch systems pre-trim candidates aggressively and cache segment speeds.

1.3 Infrastructure footprint: hybrid reality

Uber runs a hybrid footprint: owned data centers plus public cloud capacity, regionally distributed, with active-active ambitions for the most critical domains. The exact split is operationally sensitive, but the pattern is universal:

LayerTypical patternUber-relevant notes
Edge / APIAnycast DNS, CDNs, regional API gatewaysMobile APIs are versioned for years; deprecation is expensive
ComputeContainer orchestration at huge cluster scaleBin-packing, autoscaling on custom metrics (queue depth, match latency)
Service mesh / RPCgRPC + Envoy-style proxies (industry standard; Uber migrated off older stacks over time)Load balancing must be L7-aware for retries
DataSharded MySQL (Schemaless), Cassandra, Redis, Kafka, Pinot, Hudi, object storagePolyglot persistence is intentional—no single DB wins
mermaid
flowchart TB
  subgraph Mobile["Mobile clients"]
    R[Rider app]
    D[Driver app]
  end
  subgraph Edge["Edge & API tier"]
    DNS[DNS / Anycast]
    GW[API Gateway\nauth, rate limits]
  end
  subgraph Region["Regional control plane"]
    DIS[dispatch-core]
    RT[routing + traffic]
    PR[pricing]
    PY[payments / risk]
    GS[geospatial index svc]
  end
  subgraph Data["Data planes"]
    K[Kafka clusters]
    DB[(OLTP: MySQL / Schemaless)]
    CA[(Cassandra wide-column)]
    RD[(Redis cache + pubsub)]
    OLAP[(Pinot / OLAP)]
  end
  R --> DNS --> GW
  D --> DNS --> GW
  GW --> DIS
  GW --> RT
  GW --> PR
  DIS --> GS
  DIS --> RT
  DIS --> K
  RT --> K
  PR --> K
  PY --> DB
  K --> OLAP
Scale anatomy: edge API, regional services, data planes, and async logging

2. Uber's Architecture Evolution — Monolith to DOMA

Uber’s trajectory mirrors every hypergrowth company—except the spatial and economic coupling is tighter, so mistakes become visible on a heatmap within minutes.

1

2010–2012: Python monolith — speed over structure

Early Uber optimized for velocity: a Python monolith, PostgreSQL, and Google Maps. That stack shipped the MVP and proved demand. The monolith’s simplicity was a feature—until concurrency, schema contention, and release coordination became the bottleneck. The lesson: monoliths win 0→1; they lose 1→N when teams step on each other’s schemas and deploy trains grow fragile.

2

2013–2016: Service-oriented architecture (SOA) — extract by pain

Uber split the monolith into services aligned to scaling pain: dispatch, maps, payments, surge, notifications. This era introduced Node.js for high I/O services and deeper Java/Go for heavy backend paths. The lesson: early SOA is often “services by database”—still better than a single global lock, but you inherit distributed transactions problems immediately.

3

2016–present: Domain-Oriented Microservice Architecture (DOMA)

Uber formalized DOMA: organize services as domains, layers, and gateways to manage the O(n²) integration problem. Instead of ad-hoc RPC spaghetti, product engineering consumes stable gateway APIs while domain teams evolve internals. Public posts describe collection layers, extension points, and avoiding “hard” dependencies across product lines (Rides, Eats, Freight). The lesson: microservices are not the goal—clear ownership and composable contracts are.

mermaid
flowchart LR
  M[Monolith\nPython + Postgres] --> S[SOA\nextract hot paths]
  S --> D[DOMA\nDomains + Gateways\n+ extension layers]
  style M fill:#f8d7da
  style S fill:#fff3cd
  style D fill:#d4edda
Evolution: monolith → SOA → DOMA-style layering (logical)

2.1 DOMA in one paragraph

DOMA pushes teams to define domain boundaries that match business capabilities (matching, pricing, identity, payments), expose versioned APIs via gateways, and isolate experimental extensions so product lines can reuse platforms without forking them. Practically, that means fewer blocking calls across domains, more event-driven integration, and SLO-tiered dependencies (Tier-0 payment paths differ from Tier-2 recommendation features).

EraPrimary winPrimary taxMitigation
MonolithFast iterationCoupled deploys / schema fightsModularize inside monolith until extract is obvious
SOAIndependent scaleRPC fan-out, fragile chainsBulkheads, timeouts, idempotency, gateways
DOMAComposable platformsGovernance overheadStrong schemas, linters, service templates

Interview framing: why DOMA exists

When interviewers ask “microservices vs monolith,” answer with integration complexity: at Uber-scale, the problem is not deployment units—it is managing dependency graphs. DOMA is a human scalability pattern first.


3. High-Level Architecture — The Microservice Landscape

No public diagram lists every production service—nor should it. What is stable is the capability map: a few Tier-0 paths (safety, payments, trip state) and many Tier-1/2 enrichments (recommendations, incentives UI, support tooling).

3.1 Representative service map (20+ logical services)

#Logical service / capabilityRole in ride lifecycle
1API Gateway / EdgeAuthN/Z, device attestation hooks, rate limits, routing
2Session / IdentityAccounts, tokens, fraud signals at login
3Rider profilePreferences, business profiles, compliance
4Driver profile / onboardingDocuments, background checks, vehicle records
5Geospatial indexH3/S2-style partitions, online driver buckets
6Supply serviceDriver state machine: online/offline/busy
7Demand serviceTrip requests, cancellations, waitlists
8Dispatch coreMatching, rematching, timeouts
9Routing engineETA, distance matrices, polyline generation
10Traffic serviceProbe ingestion, segment speeds, closures
11Surge / dynamic pricingMultiplier computation, caps, regional policy
12Fare engineUpfront fare, time+distance, tolls, fees, currency
13Payments orchestrationTokenization, auth/capture, retries
14Risk / fraudVelocity rules, ML scores, step-up challenges
15Ledger / accountingDriver payouts, marketplace fees
16NotificationsPush (APNs/FCM), SMS, email
17Realtime gatewayWebSocket fan-out for live trip UI
18Maps & tilesBasemap, snap-to-road helpers (client + server)
19Support / triageIncident tooling, chatbots
20ComplianceData residency, audit trails, regulatory exports
21ExperimentationFeature flags, A/B pricing experiments
22Data ingestionKafka producers, schema registry clients

3.2 RPC evolution: TChannel → gRPC (+ Envoy)

Uber historically invested in TChannel / Thrift-style RPC stacks for internal performance. The industry converged on gRPC + HTTP/2 and Envoy-class sidecars for retry budgets, outlier detection, and per-route timeouts. The architectural invariant: synchronous RPC must be bounded—every hop needs deadlines propagated on the wire.

ConcernWithout mesh disciplineWith mesh + standards
RetriesRetry storms amplify outagesLimited retries + idempotency keys
TimeoutsCascading latency piles upPer-hop budgets + hedging only where safe
ObservabilityLog archaeologyTrace IDs (Jaeger lineage) + RED metrics
mermaid
flowchart TB
  subgraph Clients["Clients"]
    RA[Rider]
    DA[Driver]
  end
  subgraph Edge["Edge"]
    APIGW[API Gateway]
  end
  subgraph Core["Core domains"]
    DEM[demand-svc]
    SUP[supply-svc]
    GEO[geo-index-svc]
    DIS[dispatch-svc]
    RTE[routing-svc]
    SUR[surge-svc]
    FARE[fare-svc]
    PAY[payments-svc]
    RT[realtime-gw]
  end
  subgraph Async["Async bus"]
    K[Kafka]
  end
  RA --> APIGW
  DA --> APIGW
  APIGW --> DEM
  APIGW --> SUP
  DIS --> GEO
  DIS --> RTE
  DIS --> SUR
  DEM --> DIS
  SUP --> GEO
  FARE --> SUR
  FARE --> RTE
  PAY --> K
  DIS --> K
  RT --> RA
  RT --> DA
High-level ride path: gateway through domain services to data and streams

The "matching service" is not one loop

Production dispatch is a constellation: supply freshness, ETA precomputes, batch optimizers, fairness constraints, and reoffer logic after timeouts. Interview answers should show state machines + retries, not a single greedy function.


4. The Ride Lifecycle — Step by Step

4.1 Request creation

  1. Client validation — product available in market, payment method present, location permissions sane.
  2. Eligibility — business rules, age, region locks.
  3. Fraud / risk pre-checks — velocity on device, account history, chargeback risk.
  4. Geohash/H3 bucketing — attach request to spatial partition for dispatch.
  5. Persist intent — create trip request record with idempotency key (network retries are guaranteed).

4.2 Driver matching (dispatch)

Dispatch pulls candidate drivers from the spatial index, ranks by ETA and fairness, sends offers, handles accept/timeout, and may reoffer with backoff. Pooling adds combinatorial constraints.

4.3 Navigation and routing

Once matched, both apps need consistent ETAs and routes. The server computes authoritative ETAs for pricing and SLA; clients may use on-device navigation SDKs for turn-by-turn while periodically reconciling.

4.4 Trip tracking

High-frequency GPS is sampled, map-matched, and streamed through the realtime gateway. Riders see live location; dispatch sees updated ETAs.

4.5 Completion, fare, payment

On dropoff, the fare engine closes the trip: distance/time reconciliation, tolls, surge application, promotions, then capture against the payment instrument.

4.6 Ratings and feedback

Post-trip events update two-sided reputation systems and feed trust models—often asynchronously to keep completion latency tight.

mermaid
sequenceDiagram
  participant R as Rider app
  participant G as API Gateway
  participant D as demand-svc
  participant X as dispatch-svc
  participant S as supply-svc
  participant Q as geo-index
  participant T as routing-svc
  participant P as payments-svc
  participant K as Kafka
  R->>G: POST /requests (idempotent)
  G->>D: createRequest()
  D->>K: TripRequested
  D->>X: scheduleMatching(tripId)
  X->>Q: queryCandidates(H3, filters)
  Q-->>X: driver ids + coarse locs
  X->>T: batchETA(drivers, pickup)
  T-->>X: eta matrix
  X->>S: sendOffer(driverA)
  S-->>R: push + WS update
  R-->>G: driver accepted (client poll/WS)
  G-->>R: trip object
  Note over R,P: During trip: location ticks, reroutes, surge updates
  R->>G: completeTrip()
  G->>P: captureFare()
  P->>K: PaymentCaptured
End-to-end sequence: request → match → trip → pay → async enrichment
mermaid
stateDiagram-v2
  [*] --> REQUESTED
  REQUESTED --> MATCHING: persist + enqueue
  MATCHING --> ACCEPTED: driver accepts
  MATCHING --> CANCELLED: rider/driver/system
  ACCEPTED --> EN_ROUTE: driver started
  EN_ROUTE --> ARRIVED: at pickup
  ARRIVED --> ON_TRIP: rider on board
  ON_TRIP --> COMPLETED: dropoff
  COMPLETED --> SETTLED: fare finalized
  SETTLED --> [*]
  ON_TRIP --> CANCELLED: mid-trip cancel policy
Trip state machine (simplified — production has more edge states)
StageTypical p50 server time budgetFailure modeUser-visible fallback
Create requesttens of msDB hot shardRetry with same idempotency key
Matchhundreds of msrouting degWiden search ring, relax pool constraints
Trip updatescontinuousWS dropPoll + push rebind
Payhundreds of msprocessor outageQueue capture, show “processing”

Idempotency keys are product features

Treat Idempotency-Key like a primary key for intent. Riders double-tap; networks duplicate POSTs. Without it, you create ghost trips and double charges—both are existential incidents.


5. Geospatial Indexing — The Heart of Uber

Naive approaches—“SELECT * FROM drivers WHERE distance < 2km”—die immediately:

ApproachComplexityWhy it fails at Uber scale
Full scanO(n) per requestn = online drivers in metro → CPU meltdown
Single giant geohashHot spotsUneven partitions; thundering herds
Uncached spatial joinsDB-boundOLTP path cannot absorb spatial join storms

5.1 Google S2 geometry (deep dive)

S2 partitions the sphere into hierarchical quadrilateral cells (a space-filling curve aids locality). Each cell has a 64-bit ID, levels 0–30, and supports fast containment tests and region covering.

ConceptMeaningOperational use
CellCompact region on Earth’s surfaceBucket keys for drivers
LevelFiner cells at higher levelsTune precision vs cardinality
S2RegionCovererApproximates polygons with cellsSurge polygons, geofences, airport shapes
Cap / polygon queries“What cells intersect this region?”Demand heatmaps

Hilbert curve ordering means geographically close points often share nearby cell IDs, helping memory locality in indexes (though not perfectly—poles and edges need care).

mermaid
flowchart TB
  E[Earth sphere] --> L0[Coarse cells\nlow level]
  L0 --> L1[Subdivide\nhigher level]
  L1 --> L2[Finer cells\nmany children]
  L2 --> Q[Point query:\nlocate leaf cell]
S2-style hierarchical refinement (conceptual)

5.2 Uber H3 (hexagonal hierarchical index)

H3 is Uber’s open-source hexagonal grid. Key properties:

PropertyBenefit
Uniform adjacency6 neighbors (except pentagon artifacts) → predictable k-ring expansion
HierarchicalResolutions 0–15 from continent-scale to sub-meter class
Stable IDsInteger / string cell IDs suitable for Kafka keys and Redis sharding

k-ring expansion grows search radius in discrete steps—critical for dispatch’s “widen search if no takers” logic.

Resolution (indicative)Edge length (ballpark)Typical use
7~1.2 kmCity-scale aggregation
8~460 mNeighborhood dispatch
9~174 mDense urban refinement
10~66 mFine-grained heatmaps

Pentagons are real

H3’s icosahedron construction introduces pentagon cells with different topology. Production systems must unit test neighbor iteration around known pentagon locations—bugs here cause missed candidates.

5.3 H3 vs S2 vs Geohash vs R-tree vs Quadtree

IndexShapeHierarchyDistance / neighbor opsGreat forWeakness
H3HexagonsYesk-ringEven expansion, aggregationPentagonal edge cases
S2Quads on sphereYesCell containmentPolygons, capsSquare adjacency less uniform
GeohashRectanglesZ-order stringPrefix scansSimple KV storesEdge distortion, “seam” issues
R-treeArbitrary rectsBalanced treeWindow queriesDynamic rectanglesHigh write churn cost
QuadtreeRecursive squaresTreeLocal densityGames/simDistributed sharding harder

5.4 Java: grid-based spatial index (compilable sketch)

This is a pedagogical implementation—production adds sharding, WAL, epoch GC, and lock-free structures. It compiles standalone and shows the cell → bucket → candidate pattern.

java
// SpatialGridIndex.java — javac SpatialGridIndex.java && java SpatialGridIndex
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
 
/** Fixed grid in degrees (demo only — production uses H3/S2 IDs as keys). */
public final class SpatialGridIndex {
 
    private final double cellDegreesLat;
    private final double cellDegreesLng;
    private final Map<Long, Set<String>> cellToDrivers = new ConcurrentHashMap<>();
 
    public SpatialGridIndex(double cellDegreesLat, double cellDegreesLng) {
        this.cellDegreesLat = cellDegreesLat;
        this.cellDegreesLng = cellDegreesLng;
    }
 
    public static long cellId(double lat, double lng, double dLat, double dLng) {
        int gy = (int) Math.floor(lat / dLat);
        int gx = (int) Math.floor(lng / dLng);
        return ((long) gy << 32) | (gx & 0xffffffffL);
    }
 
    public void upsertDriver(String driverId, double lat, double lng) {
        long id = cellId(lat, lng, cellDegreesLat, cellDegreesLng);
        cellToDrivers.computeIfAbsent(id, k -> ConcurrentHashMap.newKeySet()).add(driverId);
    }
 
    public void removeDriver(String driverId, double lat, double lng) {
        long id = cellId(lat, lng, cellDegreesLat, cellDegreesLng);
        Set<String> bucket = cellToDrivers.get(id);
        if (bucket != null) {
            bucket.remove(driverId);
            if (bucket.isEmpty()) {
                cellToDrivers.remove(id, bucket);
            }
        }
    }
 
    /** Naive neighbor search: origin cell + 8 adjacent grid steps (3×3). */
    public Set<String> nearby(double lat, double lng) {
        int gy = (int) Math.floor(lat / cellDegreesLat);
        int gx = (int) Math.floor(lng / cellDegreesLng);
        Set<String> out = new HashSet<>();
        for (int dy = -1; dy <= 1; dy++) {
            for (int dx = -1; dx <= 1; dx++) {
                long id = ((long) (gy + dy) << 32) | (((long) (gx + dx)) & 0xffffffffL);
                Set<String> b = cellToDrivers.get(id);
                if (b != null) {
                    out.addAll(b);
                }
            }
        }
        return out;
    }
 
    public static void main(String[] args) {
        SpatialGridIndex idx = new SpatialGridIndex(0.01, 0.01);
        idx.upsertDriver("d1", 37.77, -122.42);
        idx.upsertDriver("d2", 37.771, -122.421);
        System.out.println(idx.nearby(37.77, -122.42));
    }
}

5.5 Millions of location updates per second

TechniqueRole
CoalescingDrop intermediate ticks; keep latest per driver per 200–500ms window
Local aggregationEdge nodes summarize before cross-region fan-in
Partition by cellKafka topic keys = H3 cell at moderate resolution
In-memory primaryRedis / custom in-memory maps with durability async
mermaid
flowchart LR
  GPS["lat/lng + accuracy"] --> CELL["cellId = H3 index at resolution"]
  CELL --> BKT[fetch bucket drivers]
  BKT --> FILT[hard filters\nvehicle, product, ban-list]
  FILT --> RANK[ETA + fairness score]
  RANK --> OFFR[send offers]
Spatial query path: lat/lng → cell id → buckets → filter → rank

6. The Dispatch System — Driver Matching

Dispatch is a continuous optimization problem disguised as an API.

6.1 Supply positioning and demand prediction

InputSourceUse
Live driver cellsgeo-indexCandidate generation
Historical demandOLAP (Pinot-like)Prepositioning incentives
Events / weatherExternal feedsSurge pre-warming

6.2 Candidate generation

  1. H3 k-ring around pickup at resolution tuned to density.
  2. Filter: product match, document validity, cooldown from prior declines.
  3. Cap to top-N before expensive ETA matrix calls.

6.3 ETA-based ranking

ETA dominates rider UX; it is the primary score before tie-breakers (fairness, idle time, quality).

6.4 Batch vs greedy

StrategyLatencyQualityWhen used
Greedylowestlocal optimumdense cities with abundant supply
Mini-batchhigherbetter global fitconstrained supply, pools, airport queues
Re-solve on tickcontinuoustracks movementlong pickup distances

6.5 Fairness and anti-gaming

ProblemMitigation sketch
Cherry-pickingThrottle repeated cancels; quality scores
Surge chasingStreak bonuses decoupled from short-hop gaming
Synthetic scarcityAnomaly detection on driver collusion patterns

6.6 Scheduled vs on-demand

Scheduled rides shift time constraints into optimization windows—dispatch pre-commits capacity closer to pickup time while sending reminder pushes.

6.7 Pool / shared rides

Pooling introduces pickup order and delay constraints for each rider. The optimizer minimizes total vehicle-minutes subject to SLA caps.

mermaid
flowchart TB
  A[New trip request] --> B[spatial candidates]
  B --> C[batch ETA]
  C --> D[score + fairness tie-break]
  D --> E[send top-K offers]
  E --> F{accept in T seconds?}
  F -- yes --> G[bind trip]
  F -- no --> H[widen k-ring / adjust surge]
  H --> B
Dispatch decision loop with timeouts and reoffer
mermaid
flowchart LR
  V[Vehicle] --> P1[Pickup A]
  P1 --> P2[Pickup B]
  P2 --> D1[Dropoff A]
  D1 --> D2[Dropoff B]
Pool matching adds ordering constraints across pickups/dropoffs

6.8 Batch ETA as a linear algebra mindset

When N drivers meet M trip offers (pooling, chained pickups), you conceptually build a cost matrix C[i][j] (ETA or earnings-adjusted ETA). Exact assignment is assignment problem / min-cost matching territory—Hungarian algorithm in O(n³) for dense matrices. Production systems rarely solve full global optima on every tick; they window the problem (top-K drivers × active trips) and warm-start from the previous second’s solution.

FormulationComplexityWhen it is worth it
Greedy min-ETAO(N log N) sortDense supply, tight latency SLO
Min-cost matching (small window)O(k³) for k×k windowAirport queues, pool batches
Column generation / heuristicsVariesVery large k with structure

Interview upgrade path

Start greedy. Then say: “If the business requires global fairness or pool optimality, I’d cap k with spatial filters, then run min-cost matching on the cap.” That signals you know OR tools without pretending the whole planet fits in one matrix.

6.9 Java: greedy ETA matcher (compilable)

java
// GreedyDispatchMatcher.java — compile with SpatialGridIndex in same dir or adjust packages
import java.util.*;
 
public final class GreedyDispatchMatcher {
 
    public record Driver(String id, double lat, double lng, double etaMinutes) {}
 
    public Optional<Driver> pickBest(Collection<Driver> candidates) {
        return candidates.stream().min(Comparator.comparingDouble(Driver::etaMinutes));
    }
 
    public static void main(String[] args) {
        var m = new GreedyDispatchMatcher();
        List<Driver> c = List.of(
            new Driver("a", 0, 0, 4.2),
            new Driver("b", 0, 0, 3.1),
            new Driver("c", 0, 0, 5.0)
        );
        System.out.println(m.pickBest(c));
    }
}

Greedy is a baseline, not the product

Interview answers should mention constraints: SLA, pool order, driver starvation, and timeouts. Greedy demos fit in a whiteboard; production adds reoptimization after each GPS tick.


7. ETA Calculation — Deep Dive

7.1 Road graph

Digital maps become a weighted directed graph: nodes are topology points; edges are road segments with distance, speed priors, turn penalties, and access rules.

RepresentationTrade-off
OpenStreetMap-derivedGreat for community updates
Proprietary fusionHigher trust for routing

OpenStreetMap — public road-graph culture

7.2 Algorithms

AlgorithmComplexityUse
DijkstraO(E + V log V)Baseline SSSP
A*Faster with heuristicInteractive routing
Contraction HierarchiesPreprocess heavy, query fastProduction-scale queries
Custom ML overlaysLearn residual errorsLast-mile calibration
mermaid
flowchart TB
  G[Road graph\nCH / CRP / custom] --> Q[Shortest path query]
  T[Live segment speeds] --> F[Fuse weights]
  Q --> F
  F --> B[Base ETA]
  M[ML calibration] --> E[Displayed ETA]
  B --> M
Routing stack: graph query + traffic overlay + ML residual

7.3 Map matching

GPS error clouds snap to polylines using hidden Markov or particle methods. Bad snapping skews distance and breaks surge borders.

7.4 Real-time traffic

StageDetail
Probe ingestionDriver phones report speeds (privacy-preserving aggregation)
Segment speedRobust estimators drop outliers
PredictionShort-horizon models for arteries
mermaid
flowchart LR
  P[Probes stream] --> A[map-match to segments]
  A --> S[speed estimator]
  S --> U[update edge weights]
  U --> R[routing queries see new costs]
Traffic fusion: probes → segment speeds → graph weight updates

7.5 ML calibration and fallbacks

When routing degrades (map incident, partial outage), fall back to haversine bounds, historical priors, and wider confidence intervals on the UI. Never silently promise impossible precision.

ETA errors are financial instruments

Upfront fares depend on predicted route minutes. Systematic underprediction angers riders; overprediction angers drivers. Calibration is a P&L problem, not only ML accuracy.


8. Surge Pricing (Dynamic Pricing)

8.1 Economics

Surge is a price signal to balance marginal supply and demand. In control-theory terms, it’s a feedback loop with human actors—latency and transparency shape stability.

8.2 Computing multipliers

FactorRole
Local imbalanceShortage in H3 polygon vs rolling baseline
Elasticity modelsHow demand responds to price
Caps / floorsRegulatory and UX guardrails
mermaid
flowchart LR
  D[Demand intensity] --> I[Imbalance metric]
  S[Supply availability] --> I
  I --> M[Multiplier function]
  M --> R[Rider price]
  M --> V[Driver incentives]
Supply/demand imbalance drives multiplier (conceptual)

8.3 Geofencing and temporal patterns

Airports, stadiums, and weather spikes produce sharp local gradients. Systems pre-materialize geofence metadata to avoid recomputing polygons on the hot path.

8.4 Anti-gaming and regulation

RiskMitigation
CollusionStatistical tests on coordinated offline events
Regulatory capsJurisdiction-specific policy tables
TransparencyShow multipliers; audit logs

8.5 Upfront vs time-and-distance

Upfront pricing fixes rider exposure but shifts risk to prediction error models—ties directly back to Section 7.

mermaid
flowchart TB
  TE[Telemetry] --> Z[Zone imbalance]
  Z --> POL[Policy engine\njurisdiction rules]
  POL --> FE[Fare engine]
  FE --> UI[Client display]
Pricing pipeline: telemetry → zones → policy → fare quote

Pricing is a control system, not a spreadsheet

If your multiplier reacts too quickly, you oscillate (hunting). Too slowly, you waste supply. Production systems apply smoothing, hysteresis, and separate rider vs driver incentive channels.


9. Payment System — Deep Dive

9.1 Fare calculation

ComponentNotes
Base fareMarket-specific
Time + distanceFrom routed polyline + clock
SurgeApplied per policy
TollsMap + provider feeds
FeesAirport, booking, service
PromotionsLedgered carefully to avoid double-spend

9.2 Methods by region

Region patternInstruments
US / EUCards, Apple Pay, PayPal
IndiaUPI, wallets
LATAM / AfricaCash-heavy markets, local rails

9.3 Auth hold and capture

Authorization reserves funds; capture happens on completion (or cancellation rules). Idempotent captures prevent duplicate charges on retries.

9.4 Fraud detection

LayerExample signals
RulesVelocity, impossible travel
MLDevice graph, session embeddings
Step-up3DS, OTP
mermaid
sequenceDiagram
  participant C as Client
  participant P as payments-svc
  participant R as risk-svc
  participant PS as Processor
  C->>P: authorize(hold)
  P->>R: score()
  R-->>P: allow / challenge
  P->>PS: AUTH
  PS-->>P: auth code
  Note over C,PS: Trip runs...
  C->>P: capture(tripId)
  P->>PS: CAPTURE
  PS-->>P: receipt
Payment orchestration sequence (simplified)
mermaid
flowchart TB
  E[Event: add card / trip pay] --> R1[rules engine]
  R1 --> R2[graph features]
  R2 --> R3[ML model ensemble]
  R3 --> DEC{Decision}
  DEC -->|allow| OK[continue]
  DEC -->|challenge| CH[3DS / OTP]
  DEC -->|block| BL[hard decline + audit]
Fraud defense in depth

9.5 Payouts, tips, promos, credits

Driver payouts batch through ledger systems with tax forms and instant pay options where supported. Tips flow as separate ledger lines. Promo codes require per-user caps and accounting segregation.

Money path is Tier-0

Degrade recommendations before you degrade authorization correctness. Payments need strong consistency within the financial boundary—often saga + outbox rather than naive “fire-and-forget Kafka.”


10. Real-Time Communication Infrastructure

10.1 WebSockets and alternatives

TransportStrengthWeakness
WebSocketDuplex, low overheadStateful proxies, reconnect storms
SSESimple server pushOne-way only
Long pollingUbiquitousHigher latency / cost
mermaid
flowchart TB
  D[Driver app] -->|GPS tick| GW[realtime-gw]
  GW -->|normalize| K[Kafka trip.topic]
  GW -->|fan-out| R[Rider subscriptions]
  subgraph Pool["Connection pool"]
    WS1[WS session]
    WS2[WS session]
  end
  R --> Pool
Realtime gateway: authenticated sessions fan out trip updates

10.2 Push notifications

APNs/FCM carry collapse keys so the latest ETA replaces stale notifications.

mermaid
sequenceDiagram
  participant S as trip-svc
  participant N as notify-svc
  participant F as FCM/APNs
  participant U as User device
  S->>N: TripStateChanged
  N->>F: send(token, payload)
  F-->>U: display / data push
Push path: event → notification service → FCM/APNs

10.3 Delivery guarantees and mobile unreliability

Assume at-least-once. Clients dedupe by (tripId, seq) or version fields. On reconnect, snapshot current trip state from an API—not from message backlog alone.

Backpressure is UX

If you blast every GPS point to riders, batteries die and routers melt. Coalesce on server; send meaningful deltas only.


11. Data Infrastructure

11.1 Kafka at Uber-scale

Public materials reference trillions of messages and thousands of topics. Operational necessities:

ConcernPattern
Multi-clusterRegional clusters + mirroring
Schema registryEvolve Avro/Protobuf safely
DLQPoison messages isolated with replay tooling
QuotaTenant-level produce limits

Kafka logo

mermaid
flowchart LR
  SVC[services] --> K[Kafka]
  K --> F[Flink jobs]
  K --> P[Pinot ingest]
  K --> H[Hudi lake]
  F --> FE[features / alerts]
Kafka as central nervous system: OLTP services produce, stream processors consume

11.2 Storage systems

StoreUber-flavored use
SchemalessSharded MySQL layer for massive OLTP
CassandraWide-column for high-write timelines
RedisCaching, rate limits, pub/sub
PinotReal-time OLAP dashboards
HudiIncremental lake ingestion

11.3 Stream processing and ML platform

Apache Flink powers real-time aggregation; Michelangelo (Uber ML platform, public talks) covers training, feature stores, and serving—connecting Kafka features to online models for ETA, fraud, and demand forecasting.

mermaid
flowchart TB
  LOG[Event logs] --> FT[Feature pipelines]
  FT --> TR[Trainer]
  TR --> REG[Model registry]
  REG --> SV[Online serving]
  RT[Realtime events] --> SV
  SV --> PR[predictions back to services]
ML platform wiring: offline training + online serving with feature parity

Training/serving skew will burn you

If offline features differ subtly from online features, models lie confidently. Invest in shared feature definitions and shadow evaluation before promoting models.

11.4 Trust, safety, and privacy-aware telemetry

Uber-scale telemetry is both a product advantage (ETA, fraud) and a civil liberties surface (movement traces). Architecturally, the same patterns appear: purpose limitation, aggregation, retention tiers, and access auditing.

CapabilityEngineering patternWhy it matters
Trip recordingsEncrypted at rest; strict ACLs; time-bounded retention policiesMinimizes blast radius of credential leaks
Real-time locationCoarse cells for analytics; finer precision only on hot pathReduces re-identification risk in logs
Safety incidentsDedicated workflows with immutable audit trailsLegal discovery and trust operations
DSAR / deletionEvent-sourced tombstones + downstream compaction jobsGDPR-style rights at pipeline scale
mermaid
flowchart TB
  P[Phone GPS] --> E[Edge gateway]
  E --> H[H3 coarse cell\nfor analytics]
  E --> D[dispatch hot path\nfiner resolution]
  H --> K[Kafka aggregate topics]
  D --> DIS[matching only]
Privacy-conscious telemetry fork: hot path keeps precision; cold path aggregates early

Safety features are Tier-0 dependencies

Emergency contacts, Share My Trip, and in-ride anomaly signals must survive partial outages. They often ride separate minimal service paths with higher replication factors than marketing banners.


12. Uber's Technology Stack

DomainLanguages / runtimesNotes
High-throughput servicesGo, JavaGC tuning, low pause collectors
Data / MLPython, Scala, JavaPyTorch/JVM interop patterns
Edge APIsNode.js historically; polyglot nowI/O heavy paths
MobileSwift, KotlinStrong typing for networking layers

12.1 Open-source footprint (sample)

ProjectUber relation
H3Created; universal spatial index
JaegerOriginated at Uber; distributed tracing
PelotonCluster scheduler (historical interest)
Kafka ecosystemHeavy producer/consumer; internal tooling

Trace IDs are the lingua franca

Adopt OpenTelemetry-compatible tracing early. At Uber/Jaeger scale, p99 tail debugging without traces is archaeology.

12.2 Schemaless in one paragraph (sharded MySQL done seriously)

Uber’s Schemaless layer (public engineering posts) is not “NoSQL in MySQL”—it is an orchestrated sharding approach with triggers, indexes, and workflows to scale wide rows and high-cardinality keys without turning every engineer into a DBA. Think application-level partitioning with consistent hashing, resharding playbooks, and online schema evolution guarded by automation.

FeatureWhat it buys youOperational cost
Hash-based shardingHorizontal write scaleCross-shard queries hurt—design around partition keys
Row-level versioningSafer migrationsCompaction + GC jobs
Async indexing pipelinesFaster writesEventual consistency windows

12.3 Framework and platform matrix (illustrative)

LayerRepresentative choicesNotes
RPCgRPC, ProtobufStrong typing across language boundaries
Mobile networkingOkHttp, Cronet, custom QUIC experimentsRetry policies must be coordinated with server idempotency
MapsOn-device nav SDKs + server authoritative ETAsSplit display vs billing responsibilities
CI/CDMassive internal pipelinesFeature flags decouple deploy from release
ObservabilityJaeger traces, metrics, structured logsRED + USE per service minimum
java
// BAD: O(n) — scans every online row per request
// SELECT driver_id FROM drivers WHERE ST_Distance(loc, ?) < 2000 AND online=1;
import java.util.ArrayList;
import java.util.List;
 
public final class NaiveSpatialQuery {
    public List<String> allWithinKm(List<DriverRow> table, double lat, double lng) {
        List<String> out = new ArrayList<>();
        for (DriverRow r : table) {
            if (haversineKm(lat, lng, r.lat, r.lng) < 2.0 && r.online) {
                out.add(r.id);
            }
        }
        return out;
    }
    private static double haversineKm(double lat1, double lon1, double lat2, double lon2) {
        final double earthKm = 6371.0;
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLon / 2) * Math.sin(dLon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return earthKm * c;
    }
    private record DriverRow(String id, double lat, double lng, boolean online) {}
}

13. Reliability and Fault Tolerance

13.1 Multi-region

Active-active patterns require conflict resolution for trip state—leader regions per trip or CRDT-like constraints are common patterns in industry.

13.2 Circuit breakers and bulkheads

PatternPurpose
Circuit breakerStop hammering sick deps
BulkheadIsolate thread pools / connection pools
Timeout + deadlineCap queue buildup
mermaid
stateDiagram-v2
  [*] --> CLOSED
  CLOSED --> OPEN: error rate > threshold
  OPEN --> HALF_OPEN: after sleep window
  HALF_OPEN --> CLOSED: probe success
  HALF_OPEN --> OPEN: probe failure
Circuit breaker states (software pattern)

13.3 Graceful degradation hierarchy

May degradeMust not degrade
Map refresh cadenceSafety incident reporting
Surge polygon granularityPayment authorization correctness
Recommendation richnessTrip state transitions integrity
mermaid
flowchart TB
  R1[Region A] -->|failing SLO| DNS[Traffic shift]
  DNS --> R2[Region B]
  R2 --> DB[(replicated data)]
Regional failover: health checks shift traffic to warm region

13.4 Chaos and SRE

GameDays inject latency, packet loss, and dependency failure to validate fallbacks. SRE practices align error budgets to feature velocity—matching Tier-0 services stricter budgets.

Degradation must be ethical and measurable

Never “fail open” on fraud or safety to save CPU. Degrade delight features first—and log when you do.

13.5 SLO starter pack and incident command

Service tierExample SLOError budget consumer
Tier-0 payments / trip state99.99% monthly availabilityFreeze risky changes near budget exhaustion
Tier-1 dispatch / routing p95< 500 ms under normal loadAuto-rollbacks + paging
Tier-2 recommendations99.5%Delay launches, not money movement
mermaid
flowchart LR
  A[Alert fires] --> B[Incident commander]
  B --> C[Mitigate: degrade / failover]
  C --> D[Status page + comms]
  D --> E[Postmortem]
  E --> F[Action items: SLO + code + runbooks]
Incident response: detect → mitigate → communicate → postmortem → fix systemic gaps
RoleResponsibility during sev-1
ICSingle decision thread; stops thrash
Tech leadRoot-cause hypotheses; rollback vs hotfix
CommsRider/driver messaging; regulator-sensitive wording
SRECapacity, circuit breakers, traffic shifts

13.6 Degradation ladder (explicit ordering)

StageUser-visible effectEngineering action
1Slightly stale map tilesServe CDN-cached tiles longer
2ETA confidence bands widenShow intervals; increase buffer in ranking
3Surge updates less granularMerge adjacent H3 cells for pricing
4Disable pooled matchingFall back to single-party offers
5Read-only dashboardsShed OLAP queries; protect OLTP
mermaid
flowchart TB
  OLAP[OLAP / BI queries] --> FEAT[Recommendations]
  FEAT --> SUR[Surge granularity]
  SUR --> RT[Realtime map polish]
  RT --> DIS[dispatch optimality]
  DIS --> PAY[Tier-0: payments + trip truth]
  style PAY fill:#d4edda
  style OLAP fill:#f8d7da
Degradation waterfall: shed load from analytics inward toward Tier-0 core

14. Lessons Learned and Key Takeaways

14.1 Patterns to steal

PatternWhen to useUber example
Spatial partitioningAny moving-asset marketplaceH3 cells + k-ring search
Idempotent command APIsMobile networksTrip request keys
Event-first integrationMany producers/consumersKafka backbone
Gateway + domain modulesMany product linesDOMA
ML calibrationPredictions affect moneyETA + upfront fare

14.2 Common mistakes in ride-sharing design

MistakeSymptomFix
Single global driver table scanDB meltdown at peakCell-partitioned index
Synchronous chains 8 hops deepTail latency explosionsCollapse with async + read models
No idempotencyDouble trips/chargesKeys + dedupe store
Ignoring fairnessDriver churnExplicit objectives in ranker

14.3 Interview tips (Uber-style system design)

  1. Start with SLO: p95 match latency, consistency model for trip state.
  2. Draw spatial index before algorithms.
  3. Separate pricing, dispatch, and payments boundaries.
  4. Discuss failure: retries, circuit breakers, regional failover.
  5. Mention observability: metrics, logs, traces for dispatch loops.

14.4 Further reading (official / authoritative)

TopicWhere to startWhat you will learn
DOMASearch Domain-Oriented Microservice Architecture on eng.uber.comHow to tame service integration graphs
SchemalessUber Engineering posts on SchemalessMySQL sharding patterns at extreme scale
MichelangeloUber Engineering posts on MichelangeloEnd-to-end ML platform concerns
H3h3geo.org + github.com/uber/h3API surface, resolutions, k-ring semantics
Jaegerjaegertracing.ioTrace topology for microservices
Kafka operationsUber Engineering + Confluent reference architecturesMulti-cluster, mirroring, replay
Dispatch intuitionEngineering posts mentioning dispatch, marketplace, matchingOffer timeouts, batching, fairness

Additional entry points:

14.5 Capacity planning thought experiment

Suppose a metro peaks at ~8k concurrent online drivers and ~15k open trip intents during a concert egress (illustrative). A 1 Hz location heartbeat (after coalescing) implies ~8k writes/s to the spatial index partition for that metro alone, before fan-out to dispatch consumers. If your design stores one giant row per city, you will serialise updates and die. If you partition by H3 cell at resolution 8–9, you spread writes across thousands of buckets—exactly the point of hierarchical spatial indexes.

KnobToo lowToo high
H3 resolution for bucketingMassive hot bucketsToo many tiny buckets; memory overhead
Offer timeoutDrivers spammed with stale offersRiders wait forever
ETA batch sizeHigh RPC overheadSlow ranking; stale decisions

14.6 Glossary (quick scan)

TermMeaning here
k-ringH3 cells within k steps on the hex grid
SurgeDynamic multiplier balancing supply/demand
SchemalessUber’s sharded MySQL storage layer (not schema-free data)
DOMADomain-Oriented Microservice Architecture
CHContraction Hierarchies (routing preprocessing)
DLQDead-letter queue for poison Kafka messages
SLOService level objective (target + window)
Tier-0Services where failure is unacceptable (payments, trip truth)

14.7 Whiteboard drills (with expected talking points)

PromptDraw firstThen addTrap answer
Design UberH3/S2 cells on a mapdispatch loop + Kafka“Just Redis GEOADD” without sharding story
Estimate Kafka throughputTopics/partitions mathbatching + compressionIgnoring consumer lag SLO
Surge pricingFeedback loop diagramcaps + smoothingStatic multiplier table
PaymentsIdempotent capture sequencerisk in parallelDouble charge on retry
Real-time mapWebSocket gatewaycoalesced GPSPer-tick DB updates

14.8 Observability checklist (production-grade)

SignalMinimum viableUber-scale upgrade
MetricsRED per serviceTail SLOs on dispatch stages
LogsJSON + trace idSampled debug + PII scrubbing
TracesSingle gateway spanFull DAG across routing + pricing
DashboardsRPS + errorsk-ring saturation, offer accept rate

14.9 Regulatory and marketplace reality (non-optional context)

Architects ignore policy tables at their peril. Cities differ on cash, vehicle class caps, dynamic pricing disclosure, and data localization. The implementation pattern is not scattered if (city == "X") branches in every service—it is versioned configuration loaded into policy engines with audit trails, evaluated close to pricing and dispatch decisions.

ConcernEngineering pattern
Price transparencyImmutable quote snapshots stored with trip
Driver classificationDocument verification workflows + expiration timers
Data residencyRegional deployments + replication constraints
mermaid
flowchart TB
  T[Telemetry] --> POL[Policy engine\njurisdiction config]
  POL --> PR[pricing outputs]
  POL --> DS[dispatch constraints]
  POL --> AUD[audit log]
Policy engine sits between raw telemetry and user-visible decisions

Epilogue: Why this problem stays fascinating

Uber’s stack is a living lesson that geometry + economics + mobile unreliability forces distributed systems to grow up fast. Build the indexes first, keep money paths boringly correct, and treat ETA as both UX and financial derivative. Do that, and you’ve earned the right to optimize the elegant details—batch matching, pooled routes, and the endless craft of fair marketplaces.

Free, no spam, unsubscribe anytime

Get every deep-dive in your inbox

New case studies, LLD patterns in Java, and HLD architectures on AWS — the full article delivered to you so you never miss a deep-dive.

Full case studies in emailJava + AWS ecosystemOne email per new article