System Design: Design a Ride-Sharing App (Uber/Lyft)
Designing Uber or Lyft is one of the most popular system design interview questions. It combines real-time geospatial queries, matching algorithms, dynamic pricing, and high-throughput event processing.
Requirements
Functional: rider requests a ride with pickup/dropoff, system matches a nearby driver, real-time tracking during the trip, dynamic surge pricing, trip history and payments.
Non-functional: match within 30 seconds, handle 1M+ concurrent users, location updates every 4 seconds per driver, sub-second driver lookup within radius, global availability.
Key Components
Rider App Driver App
│ request ride │ location ping (every 4s)
▼ ▼
┌────────────────────────────────────────────────┐
│ API Gateway / Load Balancer │
└──────────────────┬─────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Location Matching Trip
Service Service Service
(geospatial) (dispatcher) (state machine)
│ │ │
▼ ▼ ▼
Redis Geo PostgreSQL Kafka
(driver pos) (trips DB) (events)
Location Service — Driver Position Tracking
Drivers send GPS updates every 4 seconds. At 1M active drivers: 250K location updates/second.
- Redis GEO commands: GEOADD stores (longitude, latitude, member) as a sorted set with geohash scores. GEORADIUS returns all members within a radius in O(N+log(M)) — perfect for finding nearby drivers.
# Driver sends location update
redis.geoadd("drivers:active", longitude, latitude, driver_id)
redis.expire(f"driver:{driver_id}:location", 30) # expire if driver goes offline
# Find drivers within 2km of pickup
nearby = redis.georadius(
"drivers:active",
pickup_lng, pickup_lat,
radius=2, unit="km",
withcoord=True,
withdist=True,
count=50, # limit candidates
sort="ASC" # nearest first
)
Scaling: shard the drivers sorted set by geohash prefix (city or region). Drivers in NYC update drivers:active:nyc, drivers in LA update drivers:active:la.
Matching Service (Dispatcher)
- Rider requests ride with pickup + dropoff + ride_type
- Query Location Service for N nearest available drivers (radius 2km, expand to 5km if none)
- Filter by: vehicle type, driver rating, acceptance rate
- Rank by: ETA (estimated time of arrival) — requires road network routing
- Send offer to best driver (1 driver at a time or top-3 simultaneously)
- If driver accepts within 15s → assign; if declines or times out → next driver
- Timeout after 3 rounds → notify rider no drivers available
ETA Calculation
- Simple: straight-line distance / average speed × traffic factor. Fast, ~500ms.
- Accurate: road network graph (Dijkstra / A*) with real-time traffic weights. Uber uses a custom routing engine (H3 hexagonal grid). Slower but precise.
- Pre-compute ETAs for driver-to-pickup pairs in batch; serve from cache for common pairs
Trip State Machine
States:
REQUESTED → MATCHED → DRIVER_EN_ROUTE → ARRIVED → IN_TRIP → COMPLETED
└→ CANCELLED
Transitions stored in Kafka (event log) + PostgreSQL (current state)
Each state change emits an event to notify rider and driver apps via WebSocket/push
Dynamic Surge Pricing
- Compute demand/supply ratio per geohash cell every 1 minute
- surge_multiplier = f(demand / supply) — Uber uses 1.1x, 1.5x, 2x, 3x tiers
- Display surge estimate before booking; require rider to confirm surge rate
- Store surge history for analytics and model training
Payment Flow
- Upfront price estimate: base fare + per-mile + per-minute + surge + booking fee
- Final charge at trip end with actual distance/duration
- Payment processor: Stripe or Braintree; supports multiple methods (card, wallet, cash)
- Driver payout: weekly batch settlement, instant payout for premium drivers
Interview Checklist
- Draw: Location Service (Redis GEO) → Matching (dispatcher) → Trip (state machine)
- Explain Redis GEOADD/GEORADIUS for O(1) driver storage, O(log n + k) radius query
- Describe the matching loop: offer → 15s timer → next driver
- Cover surge pricing: demand/supply ratio per geohash cell
- Mention scalability: shard drivers by city, use WebSocket for real-time tracking
🏢 Asked at: Snap Interview Guide