CHAPTER 19

Design a Ride-Hailing / Proximity Service

Real-time location, geo-indexing, matching and trip state

Source: PDF pp. 56–57

Core PDF content
Functional requirementsNon-functional requirements and scale
- Rider requests a ride from A to B; see ETA and price - Match with a nearby available driver; driver accepts or declines - Live tracking of the trip; payments on completion - Drivers send location continuously- Match within a few seconds; location freshness < 5 s - Highly available; no double-booking of a driver - 10 M trips/day; 3 M drivers online sending location every 4 s - Global, with strong city-level locality
QuantityEstimateDesign implication
Trips10 M/day = ~115/s (peak ~500/s)Trip writes are modest
Location updates3 M drivers / 4 s = ~750,000 updates/sThe real scale problem: keep only the latest position in memory; stream history to Kafka
Nearby queriesOne per ride request (+ ETA refreshes)Spatial index must answer in single-digit ms
Location history750 k x ~50 B = ~3 TB/dayData lake for analytics, not the serving path
Figure 40. Ride-hailing: a high-volume location path feeds an in-memory geo index; matching queries it and drives a strongly consistent trip state machine. · PDF p. 57

Deep dives p. 57

  • Geo index: divide the world into cells (H3 hexagons or S2/geohash at ~1 km resolution). Keep cell_id -> set(driver_id, lat, lon, status, ts) in memory (Redis Cluster or a custom service), sharded by cell/city. A nearby query reads the rider's cell plus the surrounding ring(s) and expands until enough candidates are found. Drivers moving between cells are updated atomically; stale entries expire by TTL.
  • Location ingestion: drivers send compact updates over a persistent connection (WebSocket/MQTT). Overwrite only the latest position; append history to Kafka asynchronously. Adaptive frequency (slower when idle, faster during a trip) cuts load.
  • Matching: (1) find candidates in the ring; (2) rank by road ETA (not straight-line distance) from a routing engine, plus driver rating/acceptance rate; (3) send the offer to the best driver with a timeout (10-15 s), then the next. At scale, batch matching (solve an assignment problem for riders and drivers in a short window) beats greedy first-come matching.
  • No double booking: the driver's state is a small state machine (available -> offered -> assigned -> on_trip). Transition with an atomic compare-and-set (UPDATE ... WHERE state = 'available', or a Redis Lua script); if it fails, try the next candidate. Offers expire so a driver who ignores one is released.
  • Trip service: the trip state machine (requested, matched, arrived, started, completed, cancelled) lives in a strongly consistent store, sharded by trip_id/region; every transition is an event published via outbox.
  • ETA and pricing: road graphs with contraction hierarchies or precomputed tiles; live traffic from the aggregated driver pings. Surge pricing = supply/demand ratio per cell, computed by a stream job over sliding windows and smoothed to avoid oscillation.
  • Failure handling: if the geo-index shard fails, drivers re-announce within seconds (pings repopulate it); apps retry with backoff; regions operate independently (a city's data stays in its region).

Review questions for this chapter have not been written and reviewed yet. The chapter text above is the full source content.

Try it

References

System Design & AI System Design: The Complete Guide — 2026 Edition, pp. 56–57; figures 40.

Tip: use ← and → keys to move between chapters.