Core Services
A ride-sharing platform like Uber or Lyft decomposes into focused microservices:
- Location Service: ingests driver GPS pings, maintains real-time positions
- Dispatch Service: matches ride requests to nearby available drivers
- Trip Service: owns the trip state machine and trip records
- Payment Service: fare calculation, charge processing, driver payouts
- Surge Pricing Service: computes real-time multipliers by geographic cell
- Notification Service: push notifications and SMS for both rider and driver
Location Service
Every active driver pings their GPS coordinates every 4 seconds. At 1 million active drivers that is 250,000 writes per second. The design:
- Driver app sends UDP packets to a fleet of location ingest servers (UDP is acceptable — losing one ping is fine).
- Ingest servers write to Redis using
GEOADD drivers {lng} {lat} {driver_id}. Redis Geo uses a sorted set with geohash scores, enabling radius queries in O(N+log M). - Ingest servers also publish to Kafka for downstream analytics, trip event history, and persistence to Cassandra.
Dispatch Service
When a rider requests a trip:
- Call
GEORADIUS drivers {rider_lng} {rider_lat} 5 km ASC COUNT 20to get nearby drivers. - Filter by driver status = AVAILABLE.
- Score candidates by estimated ETA (from routing service) and historical acceptance rate.
- Offer trip to the top-scored driver with a 15-second accept timeout. If declined or timeout, waterfall to the next candidate.
- Once accepted, update driver status to DISPATCHED and create a trip record.
Trip State Machine
REQUESTED → DRIVER_ASSIGNED → DRIVER_EN_ROUTE → ARRIVED → IN_PROGRESS → COMPLETED
↓ ↓ ↓ ↓
CANCELLED CANCELLED CANCELLED CANCELLED
State transitions are persisted to MySQL and published to Kafka so all downstream services (notifications, payments) react to events rather than polling.
Surge Pricing
The city is divided into H3 hexagonal grid cells (~1 km diameter). The Surge Pricing Service computes, every 60 seconds:
surge_multiplier = f(open_requests / available_drivers in cell)
Multipliers are stored in Redis with a 90-second TTL. The fare estimate API reads from Redis — no DB hit needed.
Payment Service
Fare is computed at trip completion:
fare = (base_fare + distance_rate * km + time_rate * minutes) * surge_multiplier
Payment is charged asynchronously after COMPLETED event fires. Retries use exponential backoff. Driver payout is batched daily. All payment records are stored in MySQL (ACID required for money movement).
Notification Service
Consumes trip events from Kafka and sends real-time updates:
- Push notifications via APNs (iOS) and FCM (Android) for state changes
- SMS fallback (via Twilio) if push delivery fails within 5 seconds
- In-app WebSocket channel for live driver location updates to rider
Database Choices
- MySQL: trips, payments — transactional integrity required
- Redis: live driver locations (Geo), surge multipliers, driver status
- Cassandra: time-series trip event log, driver location history
- Kafka: event bus between all services
Scaling Strategy
Geo-partition the deployment: drivers and riders in NYC are handled by the US-East cluster, keeping dispatch latency under 100 ms. Dispatch servers are stateless and load-balanced using consistent hashing on region_id. Redis is clustered with slots partitioned by geohash prefix.
Key APIs
POST /rides/request → {ride_id, estimated_fare, eta}
GET /rides/{id}/status → {state, driver_location, eta}
POST /drivers/location → 200 OK
GET /rides/{id}/fare-estimate → {low, high, surge_multiplier}
Interview Tips
- Why not use a graph DB for location? Redis Geo is purpose-built for radius queries and fits in memory — graph DBs add complexity without benefit here.
- How do you handle a driver going offline mid-trip? Trip Service has a heartbeat watchdog; if no driver ping for 30s during IN_PROGRESS, alert operations.
- Surge pricing fairness: cap multiplier at 3x during emergencies (configurable policy).
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”What are the core services in a ride-sharing system like Uber?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Six core services: (1) Location Service: ingests driver GPS pings every 4 seconds, writes to Redis GEOADD for spatial queries and publishes to Kafka for persistence. (2) Dispatch Service: receives ride requests, calls GEORADIUS to find nearby drivers, scores and offers trips via waterfall dispatch. (3) Trip Service: manages the trip state machine (REQUESTED -> DRIVER_ASSIGNED -> IN_PROGRESS -> COMPLETED), stores all trip records. (4) Surge Pricing Service: maintains real-time supply/demand ratios per H3 grid cell, computes surge multiplier. (5) Payment Service: calculates fare at trip end, charges the rider via payment processor, handles splits and promotions. (6) Notification Service: sends push notifications (APNs/FCM) and SMS for status updates, ETAs, and receipts.”}},{“@type”:”Question”,”name”:”How does driver-rider matching work in a ride-sharing app?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”On ride request: (1) Call GEORADIUS on Redis to find all drivers within 5km, sorted by distance. (2) Filter by driver status=AVAILABLE and vehicle type matching the request. (3) Score remaining drivers: primary sort by ETA to pickup (derived from straight-line distance / average speed), secondary sort by acceptance rate. (4) Waterfall dispatch: offer the trip to the top-scored driver with a 15-second timeout. If they accept, match is made. If they decline or timeout, offer to the next candidate. Cap at 5 sequential offers per trip cycle. If all 5 decline, pause 30 seconds and retry with a fresh GEORADIUS query (drivers may have moved or become available). Broadcast dispatch (offering all drivers simultaneously) is avoided as it causes race conditions and driver confusion.”}},{“@type”:”Question”,”name”:”How do you track 1 million active drivers updating location every 4 seconds?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”At 1M active drivers pinging every 4 seconds: 250,000 location writes per second. Architecture: driver app sends location to a stateless Location Update Service (horizontally scaled). Service performs two writes: (1) Redis GEOADD location:{region} {lng} {lat} {driver_id} for spatial queries. (2) Redis HSET driver:{driver_id} lat {lat} lng {lng} status {status} updated_at {ts} for metadata. Asynchronously, publishes to Kafka topic driver-locations. Kafka consumers: (a) write to DriverLocationHistory Cassandra table for trip history and analytics; (b) update geofencing service for surge zone calculations. Do not write every location to MySQL – it cannot sustain 250K writes/sec. Redis handles the hot path; Cassandra handles the time-series cold path.”}},{“@type”:”Question”,”name”:”How does surge pricing work in a ride-sharing system?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Divide the city into H3 hexagonal grid cells (resolution 7, cells ~1.2km wide). For each cell, maintain in Redis: supply = count of AVAILABLE drivers with last location update < 30s ago; demand = count of REQUESTED rides in the last 5 minutes. Compute ratio = demand / supply. Map to multiplier: ratio < 0.5 = 1.0x; 0.5-1.0 = 1.2x; 1.0-2.0 = 1.5x; 2.0-3.0 = 2.0x; > 3.0 = 2.5x. The surge multiplier is shown to the rider at request time and snapshotted onto the Trip record – the rider pays the displayed price even if surge changes. Supply and demand counters are updated on driver status transitions and trip state changes. Cells are updated every 30 seconds.”}},{“@type”:”Question”,”name”:”How do you design the trip state machine for a ride-sharing app?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Trip states: REQUESTED (rider submitted, no driver yet) -> DRIVER_ASSIGNED (driver accepted, heading to pickup) -> ARRIVED (driver at pickup location) -> IN_PROGRESS (rider in vehicle, heading to destination) -> COMPLETED (at destination, fare calculated) or CANCELLED (at any state before IN_PROGRESS, with cancellation fee logic). State transitions are stored in TripEvent table: (trip_id, from_state, to_state, event_type, occurred_at, metadata). Transitions are enforced in code: only valid transitions are allowed (cannot go from REQUESTED to COMPLETED). On each transition, emit a Kafka event that triggers downstream side effects: DRIVER_ASSIGNED triggers notification to rider; COMPLETED triggers fare calculation and payment initiation. The TripEvent log enables full audit trail and replay for debugging.”}}]}
Uber system design interviews cover the full ride-sharing platform. See common questions for Uber interview: ride-sharing platform system design.
Lyft system design covers the full ride-sharing platform. Review design patterns for Lyft interview: ride-sharing app system design.
Stripe system design interviews cover payment flows in marketplace platforms. See patterns for Stripe interview: payment processing in ride-sharing system design.