What Is a Ride-Sharing System?
A ride-sharing platform matches riders requesting trips with nearby available drivers in real time, computes dynamic pricing, and tracks trips from request to completion. Examples: Uber, Lyft, DiDi. Core challenges: sub-second driver matching across millions of concurrent location updates, surge pricing computation, and ETA accuracy under traffic variability.
System Requirements
Functional
- Rider requests a trip with pickup and destination
- System finds and matches the nearest available driver
- Driver accepts or declines; if decline, offer to next driver
- Real-time location tracking of driver en route
- Dynamic (surge) pricing based on supply/demand
- Trip completion and payment processing
Non-Functional
- 5M drivers, 10M riders, 1M concurrent active trips
- Driver location updates: every 4 seconds = 1.25M updates/sec
- Match latency: <3 seconds from trip request to driver offer
Architecture Overview
Driver App ──GPS update──► Location Service ──► Redis GeoSet
Rider App ──request──► Matching Service ──► Supply Service
│
ETA Service (routing)
Pricing Service (surge)
Payment Service (on completion)
Location Service
Driver app sends GPS coordinates every 4 seconds. Location service updates Redis GeoSet:
GEOADD drivers_available lng lat driver_id
# On driver going offline or accepting trip:
ZREM drivers_available driver_id
Maintain separate GeoSets: drivers_available (accepting requests), drivers_on_trip. Sharding: partition by geohash cell across Redis nodes. 1.25M updates/sec across ~50 Redis shards = 25K ops/sec per shard — manageable.
Matching Service
def match_driver(pickup_lat, pickup_lon, rider_prefs):
# 1. Find candidates within radius
candidates = redis.georadius(
'drivers_available', pickup_lon, pickup_lat,
radius=5, unit='km', sort='ASC', count=10
)
if not candidates:
expand radius to 10km and retry
# 2. Score candidates
for driver in candidates:
score = (
0.5 * proximity_score(driver, pickup) +
0.3 * driver_rating_score(driver) +
0.2 * acceptance_rate_score(driver)
)
# 3. Offer to top-ranked driver, wait 15s for acceptance
best_driver = max(candidates, key=score)
offer_trip(best_driver, trip_id)
If the driver declines or does not respond within 15 seconds, offer to the next candidate. Track which drivers have been offered this trip (avoid re-offering).
Trip State Machine
REQUESTING ──match found──► DRIVER_ASSIGNED
│
no drivers found ──► NO_DRIVERS (retry)
DRIVER_ASSIGNED ──driver accepts──► EN_ROUTE_TO_PICKUP
EN_ROUTE_TO_PICKUP ──driver arrives──► WAITING_FOR_RIDER
WAITING_FOR_RIDER ──rider boards──► IN_PROGRESS
IN_PROGRESS ──destination reached──► COMPLETED
Any state ──cancel──► CANCELLED
Surge Pricing
Surge multiplier is computed per geographic zone (geohash cell, ~1km square). Every 30 seconds: query driver count and trip request count per zone. Compute supply/demand ratio. Apply surge if demand significantly exceeds supply:
surge_multiplier = max(1.0, sqrt(demand_rate / supply_rate))
Cap at 5x. Display clearly to rider before they confirm. Store surge_multiplier with the trip record for transparency and dispute resolution.
ETA Computation
ETA = routing_time(driver_location → pickup) + routing_time(pickup → destination). Routing: use pre-computed road graph with time-of-day weighted edges (traffic patterns). Update driver-to-pickup ETA every 30 seconds as driver moves. Destination ETA shown to rider is an estimate — recompute at pickup.
Payment
At trip completion: compute final fare = base_fare + (per_minute * duration) + (per_km * distance) * surge_multiplier. Charge the rider’s stored payment method via Stripe (with an idempotency key = trip_id). Transfer driver payout (net of platform fee) on a weekly or daily schedule to their bank account.
Interview Tips
- Redis GeoSet + GEORADIUS is the canonical answer for nearby driver search.
- Draw the trip state machine before designing any DB schema.
- Surge pricing = supply/demand ratio per geohash zone, recomputed every 30 seconds.
- Driver offer with 15-second timeout and cascade to next candidate shows real-world awareness.