sharpbyte.dev

How Uber works at scale

If you only remember the app icon, you will design the wrong system. Uber is not “a taxi app with a database.” It is a real-time marketplace: supply (drivers) and demand (riders) meet in space and time, prices change by the minute, and every trip must end with a bill that both sides trust.

In this lesson we build the design the way you would in a classroom—requirements first, numbers second, boxes third, wires last. By the end you should be able to explain what each major component does, why it exists, and how the HTTP and data paths look in production-shaped detail.

What you should be able to do after reading:

Step 0 — How we will work through the problem

Good system design is ordered thinking, not a talent for drawing clouds. Use this sequence every time someone says “design Uber”:

  1. Clarify scope. Rides only today, or Eats/Delivery too? Pooling? Scheduled rides? Say what you are not building (e.g. autonomous routing).
  2. Write requirements. Functional = what users see. Non-functional = how good “good” must be (latency, uptime, money correctness).
  3. Do napkin math. Pings per second, trips per day, bytes per trip row—so nobody assumes one server is enough.
  4. Draw three loops before you name Kafka or Cassandra.
  5. Tell one story—one rider, one driver, one successful trip—then drill into failure cases.
flowchart TB
  subgraph loc [Location loop]
    D[Driver app GPS] --> E[Edge API]
    E --> K[Kafka location stream]
    K --> G[Geospatial workers + Ringpop]
    G --> H3[(H3 cell index in memory)]
  end
  subgraph match [Matching loop]
    R[Rider request] --> DIS[Dispatch / matcher]
    H3 --> DIS
    DIS --> OFF[Offer to drivers]
    OFF --> ACC[Accept / timeout / rematch]
  end
  subgraph trip [Trip loop]
    ACC --> TSM[Trip state machine]
    TSM --> DB[(Schemaless / Cassandra trips)]
    TSM --> PAY[Payments + receipt]
  end
    

Step 1 — Functional requirements (what users and the business need)

Functional requirements describe behavior. If the system does not do these, the product is wrong—not merely slow. I group them by actor so you can map each row to a service later.

AreaRequirementWhy it is hard at scale
Rider — discovery Show which products exist here (UberX, Comfort, Black, etc.) Depends on geofence + city config, not just GPS
Rider — estimate Price range + pickup ETA before commit Needs live supply, routing, surge cell, traffic model
Rider — request Create a trip request; cancel before or after match Idempotency on flaky mobile networks
Rider — live trip Map, driver identity, ETA updates, share trip link Sub-second location stream to rider UI
Driver — supply Go online/offline; stream location while online Firehose writes, not CRUD
Driver — offers Receive trip offers; accept/reject within TTL Push + race when multiple offers overlap
Driver — navigation Pickup and dropoff guidance; complete trip State machine must match rider view
Marketplace Match rider to driver under product rules Global optimization vs greedy nearest
Pricing Surge/discount by area; show multiplier upfront Per-cell control loop on H3
Payments Authorize, capture, refund, receipt, disputes Strong consistency; never double-charge
Trust & safety Identity, fraud signals, trip sharing, support timeline Async enrichment from Kafka streams
Ops City launch, airport rules, regulatory reporting Versioned geofence configs, feature flags

Functional details easy to skip (but worth stating clearly)

Estimates expire. A price is valid for a bounded time (often minutes). If the rider waits too long, they must refresh—supply and surge moved.

Requests are not trips. A request is “I want a car.” A trip exists after a driver accepts. Confusing the two breaks your state machine.

Offers are not assignments. The matcher pushes an offer with a TTL. Until accept, the driver is not committed; the rider may still be waiting or rematched.

Cancellation policy is product logic. Fee vs free depends on state (before driver en route, after arrival, etc.)—store policy version on the trip row.

Step 2 — Non-functional requirements (how good “good” must be)

Non-functional requirements are the promises engineers make. Each one should have a target, a measurement, and a design consequence.

Category Target (typical discussion) How we meet it If we violate it
Latency — location ingest p99 < 50 ms edge accept 202 Accepted fast; async Kafka Stale map; wrong ETAs
Latency — nearby drivers p95 < 100 ms candidate set H3 index in RAM, Ringpop shard “No cars” false negatives
Latency — dispatch score p95 < 200 ms in dense city Precomputed ETAs, bounded candidate pool Rider churn at bar close
Latency — geofence p95 < 5 ms (Uber Go service cited on NYE) In-memory polygons, city-first index Wrong product at airport
Freshness — driver position < 10–15 s staleness for matching 3–5 s client ping interval Driver “teleports”; bad pickup ETAs
Availability — Marketplace 99.95%+ tier (city-visible) Redundant edge, ring failover, metro isolation City-wide outage headlines
Consistency — trip & money Linearizable trip transitions Optimistic versioning + idempotent payments Double charge, ghost trips
Consistency — GPS tiles Eventual / last-write-wins OK Overwrite by latest timestamp One bad ping—acceptable
Throughput — location Millions of pings/min globally Kafka partitioned by metro Consumer lag → stale supply
Scalability Horizontal add shards, not bigger box Ringpop, Cassandra, stateless edge Vertical ceiling in one city
Durability — trips No lost completed trip record Schemaless/Cassandra replication Support cannot help rider
Security TLS everywhere; token-bound APIs NGINX termination, OAuth2 bearer Account takeover, fraud
Privacy Minimize location retention on hot path Ephemeral index + TTL’d analytics Regulatory exposure
Observability Metrics, traces, trip event log Kafka trip.events, statsd/M3 Blind during NYE spike

Teaching point: Uber deliberately uses different consistency models on different loops. Matching can tolerate a missed ping; payments cannot tolerate a duplicate capture. Say that clearly in an exam.

Step 3 — Napkin math (so the board believes you)

Round numbers. Multiply in front of the class. You are showing thinking, not auditing Uber’s books.

Location firehose

Suppose 2 million drivers are online globally and each pings every 4 seconds. That is roughly 500,000 pings/s average—and higher at local peaks. At 200 bytes per JSON ping, you are near 100 MB/s of ingest before replication. No single MySQL primary survives that as “INSERT per ping.”

Trip records

20 million trips/day × 2 KB metadata per trip (states, fares, ids) ≈ 40 GB/day of trip-shaped rows before indexes and analytics copies—explains wide-column stores and event streams, not one normalized ledger on a laptop.

Geofence QPS

Uber’s Go geofence post cited 170k QPS on a peak night with p95 < 5 ms on tens of machines. That only works with RAM-resident polygons and a city-first index—not PostGIS per request on a cold cache.

Say out loud: “Real fleets add regions, retries, and fan-out—my numbers are order-of-magnitude.” Honest ranges beat fake precision.

Step 4 — The three loops (draw this before databases)

Start by drawing three parallel loops. If you merge them into one “Uber service,” you will put GPS in MySQL and wonder why the city caught fire.

  • Location loop — always on. Goal: “where is every online driver right now?” Storage: RAM on sharded geospatial workers.
  • Matching loop — on demand. Goal: “who should get this request, at what price?” Storage: short-lived offers + scoring features.
  • Trip loop — life of a ride. Goal: “what is the legal and financial truth of this journey?” Storage: durable trip + payment ledger.

Step 5 — Walk one ride end to end (the story examiners want)

  1. Rider opens app. Device sends lat/lng → geofence service → product list for this airport/city.
  2. Rider requests estimate. POST /v1/estimates/price → routing ETA + H3 surge cell + supply check → fare band returned.
  3. Rider confirms. POST /v1/requests with clientRequestId → request row PROCESSING → matcher wakes up.
  4. Matcher finds candidates. H3 k-ring around pickup → filter online, correct product → score ETA × acceptance × fairness.
  5. Driver gets push offer. TRIP_OFFER with TTL → driver taps accept → POST …/offers/{id}/accept.
  6. Trip record born. Status DRIVER_ASSIGNED → rider sees car on map via WebSocket stream.
  7. Lifecycle. EN_ROUTEARRIVEDON_TRIPCOMPLETED; each transition versioned in Cassandra.
  8. Payment. Capture on complete; receipt; optional rating. Events already on Kafka for analytics.

Now replay the story with failure: driver ignores offer (timeout → next driver), rider cancels (policy fee), phone loses signal (last known location + SMS/push when back).

Step 6 — Marketplace: where the real-time stack lives

Uber’s Marketplace tier is the front door: mobile edge (600+ stateless routes, NGINX + HAProxy), trip execution, matching, pricing. It has the highest availability bar in the company.

TechnologyRole in MarketplaceTeaching note
Node.js High-concurrency edge & trip orchestration Great for I/O-heavy RPC fan-out; not for point-in-polygon at 170k QPS
Go Geofence, hot optimization paths CPU + goroutines for parallel background refresh
Python → Go Pricing / ML orchestration migrating Network-bound Python clusters were over-provisioned
Kafka Location, requests, trip events Decouple ingest rate from consumer speed
Ringpop + Riak Shard ephemeral + some durable objects App-layer consistent hashing (Dynamo ideas)
Redis Hot caches, rate limits, surge counters Not source of truth for money
Schemaless / Cassandra Trip history, high write volume Tunable consistency per query

Step 7 — Four request paths (different traffic, different breaking point)

Each path answers a different question. Name the question before you name the database.

Path A — Driver location firehose

Question answered: “Where is driver D now?”

Flow: Driver app → edge API → Kafka (partition key = metroId) → geospatial worker updates in-memory index.

Why Kafka: Absorb spikes (NYE, stadium exit). Consumers can lag briefly; matching uses “latest offset per driver,” not full history.

Sanity check: If you store every ping in Postgres forever, multiply 500k inserts/s—the design falls apart immediately.

Path B — Nearby supply discovery

Question answered: “Who could pick up this rider in the next few minutes?”

Flow: Pickup lat/lng → geoToH3kRing → list drivers in those cells on the owning Ringpop shard.

Historical lesson: Uber’s pre-sharding era scanned all cars in a city—fine at small scale, impossible at millions of trips/day. Ringpop + hashing fixed horizontal scale (Ringpop blog).

Path C — Dispatch, offers, matching

Question answered: “Which driver should we ask, in what order, at what price?”

Flow: Request event → candidate set → score (ETA, acceptance model, surge, vehicle) → push offer → accept/reject/timeout.

Two eras to mention:

  • Greedy nearest — simple, fast to explain, unfair at city scale, bad for pool and balance.
  • Batched optimization — wait 2–5 s, build bipartite graph, min-cost matching for global efficiency (better ETAs city-wide).

Must implement: offerId TTL, single winner on accept, idempotent accept handler.

Path D — Trip execution, billing, history

Question answered: “What happened on this ride, and what do we charge?”

Flow: Accept → durable trip row → state transitions → payment capture → receipt → analytics from trip.events.

Must implement: optimistic locking (version column), idempotent payment id, outbox or event log for downstream search.

flowchart LR
  R[Rider lat/lng] --> H[geoToH3 + kRing]
  H --> C[Candidate drivers]
  C --> S[Score ETA + surge + acceptance]
  S --> O[Push offer]
  O -->|accept| TRIP[Trip CREATED]
  O -->|timeout| S
    

Step 8 — H3: how Uber buckets the physical world

Before H3, teams used zip codes or drawn neighborhoods—shapes change for political reasons, areas vary wildly, and squares have awkward neighbors. Uber built and open-sourced H3: a global hex grid where every event maps to a 64-bit cell id at resolution 0–15.

  • Uniform neighbors: six equidistant neighbors vs. squares with edge vs. corner distances—better for smoothing supply/demand and radius queries.
  • Less quantization error as users move through a city than rigid postal zones.
  • Hierarchical truncation: parent/child cell relationships enable rollups (block → neighborhood → city).
  • Surge cells: pricing often uses resolution ~7–8 (~0.5–1 km² per cell, varies by latitude).

API mental model: geoToH3(lat, lng, res) → 0x8a2a1072b59ffff; expand search with kRing(index, k) instead of scanning a bounding box in SQL.

// H3 indexing (conceptual — use official h3-js / h3-java bindings)
const h3Index = h3.geoToH3(37.7749, -122.4194, 9);
const ring = h3.kRing(h3Index, 2);  // origin + 2 layers of neighbors
// Supply/demand ratio per cell drives surge multiplier

Step 9 — Ringpop: sharding without making mobile clients smart

Ringpop is a library, not a database. It gives you Dynamo-style partitioning in the application: SWIM gossip for membership, consistent hashing (FarmHash) for keys, handle-or-forward so any node can receive a request.

Why this matters: Mobile apps should not embed shard maps. They hit the edge; Ringpop routes to the node that owns driver driverId or cell bucket. When a node dies, neighbors take its key range—no manual resharding meeting.

Step 10 — Geofence service: when hexagons are not enough

Not every rule is a hexagon. Airports, toll zones, and product bans use human-drawn polygons. Uber’s dedicated Go geofence microservice answers “which polygons contain this point?” on every location-sensitive request.

  • Two-level index: find city polygon first, then scan only geofences inside that city (cuts N from 10⁴ to ~10²).
  • In-memory index refreshed in background; RWMutex or atomic swap for readers/writers.
  • Why Go: CPU-bound point-in-polygon at 100k+ QPS without blocking the Node event loop.

Step 11 — Surge pricing as a control system

Teach surge as feedback control, not greed. Each H3 cell maintains a ratio: demand (requests, sessions) vs supply (idle drivers). The multiplier rises when ratio > 1 for a sustained window; riders see it in POST /v1/estimates/price before they commit.

  • Inputs stream on Kafka; aggregators maintain per-cell counters in memory or Redis.
  • Outputs feed pricing on GET /estimates and dispatch scoring (drivers chase high-multiplier areas).
  • Guardrails cap multipliers, damp oscillations, and enforce policy (regulatory caps by city).

Step 12 — Trip state machine (memorize the states)

stateDiagram-v2
  [*] --> PROCESSING: rider POST /requests
  PROCESSING --> DRIVER_ASSIGNED: driver accept
  DRIVER_ASSIGNED --> EN_ROUTE: driver moving to pickup
  EN_ROUTE --> ARRIVED: at pickup
  ARRIVED --> ON_TRIP: rider aboard
  ON_TRIP --> COMPLETED: dropoff
  PROCESSING --> CANCELLED: rider cancel
  DRIVER_ASSIGNED --> CANCELLED: policy allows
  COMPLETED --> [*]
  CANCELLED --> [*]
    

Every arrow is a versioned transition. Illegal transitions return 409 Conflict. Payment capture hooks attach only to allowed terminal states.

Step 13 — Storage: match the loop to the store

DataTypical storeWhy
Latest driver position Ringpop-sharded RAM (+ optional Redis) Sub-10 ms reads; loss of 1 ping is OK
Trip, fare, status history Schemaless / Cassandra High write throughput, wide rows, tunable consistency
Geofence definitions Datastore + on-disk snapshot for fast boot Read-heavy, versioned configs
Analytics, ML features Kafka → Hive/Spark, Elasticsearch Offline training, dashboards, fraud
Payments & ledger Strongly consistent financial store Cannot “eventual consistency” money

Step 14 — Technical layer: APIs, payloads, and wires

Requirements tell you what; this section shows how engineers ship it. Paths are illustrative but shaped like Uber’s documented edge (NGINX → Node mobile API → internal gRPC). Learn to read them the way you would read an on-call runbook: method, auth, body, status codes, side effects on Kafka.

API map

Layer Typical path prefix Auth Role
Mobile edge (BFF) /rt/mobile/*, /v1/* OAuth2 bearer + device attestation Aggregate calls; TLS terminated at NGINX
Driver telemetry POST /v1/locations Driver session token High-frequency GPS ingest
Rider marketplace /v1/estimates, /v1/requests Rider session token Price, request, cancel trip
Dispatch (internal) POST /dispatch/v1/offers mTLS service identity Matcher → driver push gateway
Geofence GET /v1/geofences/contains Internal service token Point-in-polygon + product rules

Driver location ping

Sent every few seconds while online. Idempotent on (driverId, clientSeq); server keeps latest by timestamp.

OperationHTTPSuccessNotes
Upload location POST /v1/drivers/{driverId}/locations 202 Accepted Body < 1 KB; batched on client optional
Go online / offline PUT /v1/drivers/{driverId}/status 200 Triggers supply index add/remove
POST /v1/drivers/8f3c…/locations HTTP/1.1
Authorization: Bearer drv_…
Content-Type: application/json

{
  "clientSeq": 1842091,
  "recordedAt": "2026-05-19T18:04:11.123Z",
  "point": { "lat": 37.7749, "lng": -122.4194 },
  "accuracyM": 8.2,
  "bearingDeg": 142,
  "speedMps": 6.4
}

→ 202 Accepted
→ async: Kafka topic driver.locations.v1 (key=metro:sf)
→ consumer updates Ringpop shard + H3 cell 8928308280fffff

Rider: estimate and request

OperationHTTPSuccessCommon errors
Price / ETA estimate POST /v1/estimates/price 200 + fare breakdown 400 unserviceable area, 503 no supply
Request trip POST /v1/requests 201 + requestId 409 duplicate clientRequestId
Poll / stream status GET /v1/requests/{id} or WebSocket 200 404 after TTL if cancelled
Cancel POST /v1/requests/{id}/cancel 200 409 already matched / on trip
POST /v1/estimates/price HTTP/1.1
Authorization: Bearer rdr_…
Content-Type: application/json

{
  "pickup":  { "lat": 37.7765, "lng": -122.4168 },
  "dropoff": { "lat": 37.6213, "lng": -122.3790 },
  "productId": "uberX",
  "paymentProfileId": "pm_…"
}

→ 200 OK
{
  "estimateId": "est_9a2…",
  "pickupEtaSec": 240,
  "fare": {
    "currency": "USD",
    "low": 24.50,
    "high": 28.10,
    "surgeMultiplier": 1.4,
    "h3Cell": "8928308280fffff"
  }
}
POST /v1/requests HTTP/1.1
Authorization: Bearer rdr_…
Idempotency-Key: 7f1c8e2a-…

{
  "clientRequestId": "7f1c8e2a-…",
  "estimateId": "est_9a2…",
  "pickup":  { "lat": 37.7765, "lng": -122.4168 },
  "dropoff": { "lat": 37.6213, "lng": -122.3790 },
  "productId": "uberX",
  "paymentProfileId": "pm_…"
}

→ 201 Created
{
  "requestId": "req_4b1…",
  "status": "PROCESSING",
  "expiresAt": "2026-05-19T18:09:00Z"
}

Dispatch offer (driver-facing, push)

Matcher assigns an offerId with TTL (often 10–20 s). Driver app accepts or rejects; server enforces single winner.

// Push payload (APNs/FCM + in-app modal) — illustrative
{
  "type": "TRIP_OFFER",
  "offerId": "off_88c…",
  "expiresAt": "2026-05-19T18:05:02Z",
  "pickup": { "lat": 37.7765, "lng": -122.4168, "address": "…" },
  "dropoff": { "lat": 37.6213, "lng": -122.3790 },
  "estimatedEarnings": { "currency": "USD", "amount": 18.40 },
  "surgeMultiplier": 1.4
}

POST /v1/drivers/{driverId}/offers/{offerId}/accept
→ 200 { "tripId": "trip_2d9…", "status": "DRIVER_ASSIGNED" }

POST …/reject  → 204 (matcher may offer next driver)

Geofence lookup (internal, Go service)

GET /v1/geofences/contains?lat=37.6213&lng=-122.3790&layers=airport,city,product

→ 200 OK
{
  "cityId": "san_francisco",
  "matches": [
    { "geofenceId": "SFO_TERMINAL_1", "type": "airport", "rules": ["PICKUP_ZONE_A"] }
  ],
  "productsAvailable": ["uberX", "comfort", "black"]
}

Trip state machine and persistence

-- trips (Cassandra / Schemaless — illustrative columns)
CREATE TABLE trips (
  trip_id          text PRIMARY KEY,
  rider_id         text,
  driver_id        text,
  status           text,  -- CREATED → DRIVER_ASSIGNED → EN_ROUTE → ON_TRIP → COMPLETED | CANCELLED
  client_request_id text,
  fare_estimate_id text,
  pickup_h3        text,
  created_at       timestamp,
  version          int      -- optimistic concurrency
);

-- Idempotent accept
UPDATE trips SET status='DRIVER_ASSIGNED', driver_id=?, version=version+1
WHERE trip_id=? AND status='CREATED' AND version=?
IF EXISTS;

Status transitions emit trip.events on Kafka for maps, support, and analytics. Payment capture runs only on COMPLETED (or policy-defined cancel fees).

Realtime trip updates (rider app)

TransportWhenPayload
WebSocket /v1/trips/{id}/stream Active trip { status, driverLocation, etaSec } every 1–3 s
HTTP poll fallback Background / flaky networks GET /v1/trips/{id}

Kafka topics (illustrative names)

TopicPartition keyConsumers
driver.locations.v1 metroId Geospatial index, surge aggregator, fraud
rider.requests.v1 cityId Dispatch matcher, demand forecasting
trip.events.v1 tripId Maps, receipts, data warehouse

Payments (must be idempotent)

POST /v1/trips/trip_2d9…/payment/capture HTTP/1.1
Authorization: Bearer internal…
Idempotency-Key: cap_7f1c8e2a-…

{ "amount": 2840, "currency": "USD", "paymentProfileId": "pm_…" }

→ 200 { "chargeId": "ch_…", "status": "CAPTURED" }
→ 409 if trip.status != COMPLETED
→ 409 duplicate Idempotency-Key replays same chargeId (safe retry)

Trip read API (rider poll or support)

GET /v1/trips/trip_2d9… HTTP/1.1
Authorization: Bearer rdr_…

→ 200 OK
{
  "tripId": "trip_2d9…",
  "status": "ON_TRIP",
  "version": 5,
  "driver": { "id": "8f3c…", "vehicle": "Toyota Camry", "plate": "…" },
  "pickup": { "lat": 37.7765, "lng": -122.4168 },
  "dropoff": { "lat": 37.6213, "lng": -122.3790 },
  "fareSnapshot": { "currency": "USD", "surgeMultiplier": 1.4 },
  "driverLocation": { "lat": 37.7801, "lng": -122.4102, "recordedAt": "…" }
}

Rate limits and errors (teach the status codes)

CodeWhenClient behavior
400 Bad coordinates, unsupported product in geofence Show validation message
401 / 403 Expired token, wrong role (rider token on driver API) Re-auth
409 Duplicate clientRequestId, illegal state transition Fetch existing resource; do not blind retry
429 Rate limit on estimates or location spam Honor Retry-After
503 Matcher overload, geospatial shard recovering Backoff; show “high demand”
  • Location API: token bucket per driver (~1 req/s sustained, burst 3).
  • Estimates: cache by (pickupH3, dropoffH3, productId) for 30–60 s.
  • Idempotency: always on POST /requests and payment capture.

Service-to-service calls (behind the edge)

CallerCalleeProtocolPurpose
Edge API Geofence gRPC / HTTP Product availability, airport rules
Edge API Pricing gRPC Fare estimate from route + surge cell
Dispatch Geospatial index gRPC Candidate drivers for H3 ring
Dispatch Push gateway HTTP APNs/FCM offer to driver
Trip service Payments gRPC + idempotency key Authorize/capture

Step 15 — Reliability, security, and observability

Reliability patterns

  • Handle-or-forward (Ringpop): any node can receive; library routes to owner—clients stay dumb.
  • Offer TTL + rematch: no infinite “searching for driver” without state change.
  • Metro isolation: Kafka + rings partitioned by city—São Paulo does not starve Stockholm.
  • Graceful degradation: widen ETA bands, hide pool, show “high demand” before 500ing the app.

Security checklist

  • TLS 1.2+ to NGINX; bearer tokens scoped to rider vs driver.
  • Rate limits on location POSTs—stolen accounts should not spam pings.
  • Internal dispatch APIs on mTLS, not public internet.
  • PII minimization on hot path; retention policies on location history.

Observability every team wires up

  • Metrics: QPS, p95 latency per endpoint, Kafka lag, match rate, offer accept rate.
  • Traces: one trace id from POST /requests through match to tripId.
  • Logs: structured trip events—not printf debugging in production.
  • Business dashboards: supply/demand per H3 cell for ops centers.

Step 16 — Exam cheat sheet: goals → knobs

GoalKnob
Fast “cars nearby”H3 k-ring size, in-memory index, Ringpop sharding
Fair city-wide matchingBatched optimization, acceptance models, rematch timeouts
Balance supply/demandPer-cell surge, driver incentives, heatmaps for positioning
Correct moneyTrip state machine + idempotent payments, not “last write wins” on GPS
Survive NYE peakKafka scale, metro isolation, Go for hot paths, cache geofences in RAM

Step 17 — Close the loop (what to practice)

On a whiteboard: draw three loops, write one trip story, label H3 + Ringpop + trip DB on the right loop.

Out loud: recite five functional requirements and one non-functional target for each loop.

In code reading practice: pick any POST below and list headers, idempotency key, Kafka topic, and resulting state change.

Deeper public reading: H3, Ringpop, Geofence in Go, Marketplace tech stack, H3 source.

The one line to remember

Uber is three systems wearing one icon: a live map (location loop), a market matcher (matching loop), and a contract (trip loop). Teach those separately; implement APIs that respect each loop’s consistency and latency—and the design will look like you have been in the room where it was built.