High-Level Design

Capacity Estimation

Start from the requirement numbers and derive everything else. Show the arithmetic — an interviewer wants to see the reasoning, not a memorised answer.

Traffic

MetricCalculationResult
Location write QPS (avg)5M drivers ÷ 4 s1.25M writes/s
Location write QPS (peak ×2)1.25M × 22.5M writes/s
Ride requests/s (avg)8M ÷ 86,400 s~93/s
Ride requests/s (peak ×5)93 × 5~465/s
Concurrent rides (avg)8M rides × 20 min ÷ 1,440 min/day~111k
Concurrent rides (peak ×3)111k × 3~333k
WebSocket connections (peak)333k riders + 333k drivers~666k
Matching ops/s (peak)~same order as ride requests~500/s

The location-update path is the dominant write load — 2.5M/s at peak. Ride-match operations are comparatively tiny but require low latency.

Storage

Driver location — in-memory, current state only:

FieldBytes
driver_id (int64)8
lat / lon (float64 each)16
status (enum byte)1
timestamp (int64)8
Per driver~33 B

5M drivers × 100 B (with metadata/padding) = ~500 MB — the entire current-location table fits comfortably in a Redis cluster.

Location history — analytics and fraud, not on the hot path:

| Metric | Calculation | Result | |—|—| | Events/day | 1.25M/s × 86,400 s | ~108B events/day | | Raw bytes/day | 108B × 33 B | ~3.6 TB/day | | Compressed (×0.15) | 3.6 TB × 0.15 | ~540 GB/day |

Store compressed Parquet partitions in object storage (Kafka sink). The hot path never touches historical location data.

Ride records:

| Metric | Calculation | Result | |—|—| | Bytes/ride | ~1 KB | — | | Rides/day | 8M | — | | Storage/day | 8M × 1 KB | 8 GB/day | | 5-year retention | 8 GB × 365 × 5 | ~14.6 TB |

A modestly sharded relational store handles ride records comfortably.

Memory (hot operational state)

TierWhatSize
Geo-index (current driver locations)5M drivers × 100 B~500 MB
Surge multiplier cache~1M H3 cells × 16 B~16 MB
Active ride-state cache333k rides × 200 B~67 MB
Total hot state< 1 GB

All hot operational state fits in a small Redis cluster (3–6 nodes with replicas).

Derived Infrastructure

ComponentSizing rationaleCount
Location ingestion nodes2.5M req/s peak; ~125k/node~20 stateless nodes
Kafka partitions~50k writes/s per partition (safe limit)~50 partitions
Location consumer nodesBatch Redis GEOADD at ~250k updates/s/node~10 nodes
Ride / Matching app nodes500 ops/s peak; trivial~10 nodes
WebSocket gateway nodes~10k connections/node~70 nodes
Redis cluster (geo-index)< 1 GB hot state + replication3-node cluster × 2

API Design

A REST-ish contract. All rider and driver calls are authenticated via JWT at the API Gateway. Internal service calls use mTLS.

Rider APIs

POST /api/v1/rides
  Body:    { "pickup_lat": 37.77, "pickup_lon": -122.42,
             "dest_lat": 37.80, "dest_lon": -122.40 }
  201  →  { "ride_id": "r_abc123", "status": "matching",
             "estimated_fare_usd": 12.50, "surge_multiplier": 1.2,
             "estimated_wait_seconds": 240 }
  503  →  No drivers available in area

GET /api/v1/rides/{ride_id}
  200  →  { "ride_id", "status", "driver": { "id", "name", "lat", "lon",
             "eta_seconds", "vehicle" }, "act_fare_usd" }
  404  →  Ride not found

DELETE /api/v1/rides/{ride_id}
  204      (rider cancels before pickup; cancellation fee may apply)

GET /api/v1/rides/{ride_id}/track
  101      WebSocket upgrade; server pushes { "lat", "lon", "eta_seconds" } every ~4 s

Driver APIs

POST /api/v1/drivers/location
  Headers: X-Driver-Token: <jwt>
  Body:    { "lat": 37.77, "lon": -122.42, "status": "available" }
  204      (fire-and-forget; failures are silently retried by driver app)

POST /api/v1/rides/{ride_id}/offers/{offer_id}/accept
  204      Driver assigned; ride transitions to "accepted"
  409      Offer expired or already taken by another driver

POST /api/v1/rides/{ride_id}/offers/{offer_id}/reject
  204

POST /api/v1/rides/{ride_id}/complete
  Body:    { "trip_distance_km": 5.3, "duration_minutes": 18 }
  200  →  { "fare_usd": 14.70, "driver_payout_usd": 11.76 }

Internal APIs

GET /internal/drivers/nearby
  ?lat=37.77&lon=-122.42&radius_km=5&status=available&limit=20
  200  →  [ { "driver_id", "lat", "lon", "distance_km", "eta_seconds" } ]

Data Model

Entities and Stores

EntityKey FieldsStoreRationale
Driver (profile)driver_id PK, name, car_info, ratingPostgreSQL (replicated)Low write rate; relational for reporting
DriverLocation (current)driver_id PK, lat, lon, h3_cell, status, updated_atRedis Hashes + H3 SetsSub-ms geo-queries; pure in-memory fits easily
Rider (profile)rider_id PK, name, payment_method_idPostgreSQLSame as driver profile
Rideride_id PK, rider_id, driver_id, status, origin, dest, fare, timestampsPostgreSQL (sharded)Strong consistency for billing; relational audit trail
Offeroffer_id PK, ride_id, driver_id, expires_atRedis + TTLShort-lived; natural auto-expiry via TTL
SurgeMultiplierh3_cell_id PK, multiplier, computed_atRedisUpdated every 30–60 s; must be read fast

Ride State Machine

requested → matching → offered → accepted → en_route → in_progress → completing → completed
                                  ↘ rejected or timed-out → (next candidate)
           ↘ no drivers found → cancelled

Schema Sketches

-- Rides (sharded by ride_id)
CREATE TABLE rides (
  ride_id       UUID             PRIMARY KEY,
  rider_id      BIGINT           NOT NULL,
  driver_id     BIGINT,
  status        TEXT             NOT NULL,
  pickup_lat    DOUBLE PRECISION NOT NULL,
  pickup_lon    DOUBLE PRECISION NOT NULL,
  dest_lat      DOUBLE PRECISION NOT NULL,
  dest_lon      DOUBLE PRECISION NOT NULL,
  surge_mult    NUMERIC(4,2)     NOT NULL DEFAULT 1.0,
  est_fare_usd  NUMERIC(8,2),
  act_fare_usd  NUMERIC(8,2),
  created_at    TIMESTAMPTZ      NOT NULL DEFAULT now(),
  completed_at  TIMESTAMPTZ
);
-- Redis: per-driver hash
HSET driver:{driver_id}  lat 37.77  lon -122.42  h3_cell 8928308280fffff  status available  ts 1700000000

-- Redis: per-H3-cell available-driver set (updated on every move)
SADD h3:8928308280fffff:available  driver_id_1  driver_id_2 ...

High-Level Architecture — v1

Level 0 — Context

flowchart LR
    RA["Rider App"] -->|request ride / track trip| RS["Ride-Sharing Platform"]
    DA["Driver App"] -->|GPS ping every 4s| RS
    RS -->|match offer + navigation| DA
    RS -->|status + live location| RA
    RS -->|charge card| PG["Payment Gateway"]

Level 1 — First-Cut Components

flowchart TB
    subgraph Clients
      RA["Rider App"]
      DA["Driver App"]
    end

    subgraph EdgeLayer["Edge"]
      LB["API Gateway / LB"]
      WSG["WebSocket Gateway"]
    end

    subgraph CoreSvcs["Core Services"]
      LocSvc["Location Service"]
      MatchSvc["Matching Service"]
      RideSvc["Ride Service"]
      PaySvc["Payment Service"]
    end

    subgraph DataStores["Storage"]
      GeoIdx[("Geo-Index
Redis GEO")] RideDB[("Ride DB
PostgreSQL")] SurgeCache[("Surge Cache
Redis")] end subgraph Async KF[["Location Kafka Topic"]] PayQ[["Payment Queue"]] end DA -->|POST /drivers/location| LB --> LocSvc LocSvc --> KF --> GeoIdx RA -->|POST /rides| LB --> RideSvc RideSvc --> RideDB RideSvc --> MatchSvc MatchSvc -->|query nearby drivers| GeoIdx MatchSvc -->|push offer| DA DA -->|accept or reject| LB --> RideSvc RA -->|WebSocket| WSG DA -->|GPS during trip| WSG WSG -->|push location to rider| RA RideSvc -->|trip complete| PayQ --> PaySvc PaySvc -->|charge| PG["Payment Gateway"]

Component responsibilities:

  • API Gateway / LB. TLS termination, JWT authentication, rate limiting, and routing to stateless backend services. Scales horizontally.
  • Location Service. Accepts driver GPS pings, validates the token, and publishes updates to Kafka. A consumer group reads from Kafka and applies updates to the in-memory geo-index. The Kafka buffer decouples ingestion from index latency. See /dev-docs/design-concepts/specialized/location-indexing/.
  • Geo-index (Redis). Stores current driver positions. In v1 this uses Redis’s built-in GEO sorted set (GEOADD/GEORADIUS). Answers “nearby available drivers” queries in O(log N + results).
  • Matching Service. Queries the geo-index for nearby available drivers, ranks by ETA, and dispatches offers one at a time. Stateless — any node can handle any match request.
  • Ride Service. State-machine owner for each ride. Persists ride records, manages offer lifecycle (with TTL), and coordinates status transitions.
  • WebSocket Gateway. Maintains long-lived connections for riders and drivers. Receives driver location updates via pub/sub and forwards them to the matching rider’s socket. See /dev-docs/design-concepts/networking/realtime-transport/.
  • Payment Service. Processes charges at trip completion with idempotency keys to prevent double-charging. See /dev-docs/design-concepts/distributed/idempotency/.
  • Kafka (location topic). Buffers 1.25M+ writes/s, decouples the ingestion front-door from geo-index consumers, and provides a durable replay log. See /dev-docs/design-concepts/messaging/kafka/.

The weaknesses of v1 — absorbing the location firehose, the geospatial index algorithm, the matching cascade, surge pricing, WebSocket scaling, and payment idempotency — are addressed in the deep dives.