System Design Interview: Design a Ride-Sharing App (Uber/Lyft Dispatch)

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.

  • Airbnb Interview Guide
  • Stripe Interview Guide
  • Snap Interview Guide
  • DoorDash Interview Guide
  • Lyft Interview Guide
  • Uber Interview Guide
  • 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.

    {
    “@context”: “https://schema.org”,
    “@type”: “FAQPage”,
    “mainEntity”: [
    {
    “@type”: “Question”,
    “name”: “How does a ride-sharing platform find the nearest available drivers in under 3 seconds?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “Nearest driver search uses Redis GeoSet with GEORADIUS. Every active driver sends a GPS update every 4 seconds; the location service updates their position: GEOADD drivers_available longitude latitude driver_id. GEORADIUS drivers_available pickup_lon pickup_lat 5 km ASC COUNT 10 returns up to 10 nearest drivers sorted by distance in O(log N + K) time. For 5M drivers across all markets, shard by geohash prefix across ~50 Redis nodes — each shard handles one geographic region. GEORADIUS for a 5km radius in one city queries a single shard containing ~50K drivers in that city, returning in under 5ms. Total request path: rider request → matching service → Redis GEORADIUS → score candidates → offer to top driver: under 200ms. The 3-second SLA includes the driver app receiving and displaying the offer (network RTT) and the driver having time to accept.” }
    },
    {
    “@type”: “Question”,
    “name”: “How does surge pricing work and how do you compute it efficiently?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “Surge pricing adjusts ride costs when demand exceeds supply in a geographic area. Implementation: divide the service area into geohash cells (~1km x 1km squares at precision 6). Every 30 seconds, a pricing service queries Redis for: (1) active driver count per cell (drivers_available GeoSet members in the cell), (2) trip request rate per cell (from a sliding window counter in Redis). Surge multiplier = max(1.0, sqrt(demand_rate / supply_rate)). Cap at 5x. Store results in a Redis hash: surge:{geohash_cell} → multiplier with 60-second TTL. At trip request time: determine the pickup geohash cell, look up the surge multiplier in O(1). Compute the fare estimate: base_fare * surge_multiplier. Display prominently to the rider before they confirm. Store the surge_multiplier in the bookings table for auditing — riders who were charged surge can dispute if it was applied incorrectly. The 30-second recompute cadence balances responsiveness with compute cost.” }
    },
    {
    “@type”: “Question”,
    “name”: “How do you handle the driver offer cascade when a driver declines a trip?”,
    “acceptedAnswer”: { “@type”: “Answer”, “text”: “When a trip request arrives, the matching service ranks nearby drivers and offers to the top-ranked candidate. The driver has a 15-second acceptance window (configurable). If they decline or do not respond: mark this driver as "offered this trip" (store in Redis set: offered:{trip_id} → {driver_id} with TTL matching the trip lifetime). Offer to the second-ranked driver. Repeat up to a configurable max (e.g., 5 drivers). After 5 refusals: expand the search radius and repeat. After the radius reaches a maximum (e.g., 20km) or a total timeout (e.g., 90 seconds): return "no drivers available" to the rider. Driver state transitions on each offer: move from drivers_available to a "considering offer" state; move back to available if they decline or timeout. This prevents the same driver from receiving multiple concurrent offers. Drivers who frequently decline have their acceptance_rate tracked — it factors into the matching score, gradually offering trips to more reliable drivers first.” }
    }
    ]
    }

    Scroll to Top