Designing a ride-sharing application like Uber or Lyft is one of the most comprehensive system design questions. It combines real-time location tracking, matching algorithms, dynamic pricing, payment processing, and notification systems. This guide covers the end-to-end architecture — from ride request to trip completion — with the depth expected at senior engineering interviews at top companies.
High-Level Architecture
Core services: (1) Location Service — ingests driver GPS updates (every 3-5 seconds from the driver app), stores current positions in a geospatial index for fast proximity queries. (2) Matching Service — when a rider requests a ride, finds the best available driver based on proximity, ETA, driver rating, and vehicle type. (3) Pricing Service — calculates the fare using base price, distance, time, and surge multiplier. Computes upfront pricing (the rider sees the price before confirming). (4) Trip Service — manages the trip lifecycle: requested, driver_assigned, driver_en_route, arrived, trip_started, trip_completed, trip_cancelled. (5) Payment Service — processes payment after trip completion, handles tips, refunds, and driver payouts. (6) Notification Service — sends real-time updates to rider and driver via push notifications and WebSocket. Data flow: rider requests a ride -> pricing service computes fare -> rider confirms -> matching service finds the best driver -> driver accepts -> location updates stream to rider -> driver arrives -> trip starts -> trip completes -> payment processes -> both parties rate each other.
Driver Location Tracking
Each active driver sends GPS coordinates every 3-5 seconds. With 1 million active drivers: 200,000-333,000 location updates per second. Storage: the geospatial index only needs the latest position per driver (not a history). Use Redis with the GEO data type: GEOADD drivers longitude latitude driver_id. To find nearby drivers: GEOSEARCH drivers FROMLONLAT longitude latitude BYRADIUS 5 km ASC COUNT 20 returns the 20 nearest drivers within 5 km. Redis GEOSEARCH runs in O(N + log M) where N is results and M is total entries — fast enough for real-time queries. For higher scale, shard the geo index by geographic region (geohash prefix). Each shard handles drivers in a specific area. Location history: store the GPS trail in a time-series database (InfluxDB, TimescaleDB) or append to Kafka for downstream processing (ETA models, route optimization, fraud detection). The real-time index (Redis) is separate from the historical store. WebSocket for rider updates: once matched, the rider app receives driver location updates via WebSocket every 3-5 seconds. The server subscribes to the matched driver location stream and pushes to the rider.
Matching Algorithm
When a rider requests a ride, the matching service finds the optimal driver. Algorithm: (1) Geographic search — query the geospatial index for available drivers within a radius of the pickup location. Start with 3 km, expand to 5 km if insufficient. (2) ETA calculation — for each candidate driver, compute the estimated time of arrival to the pickup point. Use a routing API (Google Maps, OSRM) or a pre-computed travel time matrix (geohash-to-geohash lookup table, updated hourly from historical GPS data). (3) Ranking — rank candidates by: ETA (closest first), driver rating (higher rated preferred), trip acceptance rate, and vehicle match (rider requested an SUV, only match SUV drivers). (4) Offer — send the ride request to the top-ranked driver with a 15-30 second acceptance window. If declined or timed out, offer to the next driver. Uber optimization: instead of offering to one driver at a time (sequential, slow), send the request to the top 3-5 candidates simultaneously. The first to accept gets the trip. Others are released. This reduces total matching time from potentially 60-90 seconds (3 sequential 30-second timeouts) to under 15 seconds. Batched matching: for high-demand areas, accumulate ride requests for a few seconds and solve the optimal assignment globally (Hungarian algorithm or linear programming), minimizing total wait time across all pending riders.
Surge Pricing
Surge pricing increases fares when demand exceeds supply in a geographic area. Purpose: (1) Attract more drivers to high-demand areas (higher earnings incentive). (2) Reduce demand from price-sensitive riders (fewer requests, shorter wait times). (3) Balance supply and demand dynamically. Implementation: divide the city into hexagonal zones (H3 geospatial indexing). For each zone, compute supply (number of available drivers) and demand (number of ride requests in the last 5 minutes). Surge multiplier = f(demand / supply). If demand is 3x supply, the multiplier might be 2.0x. The function is typically a step function or sigmoid: no surge when demand/supply < 1.2, gradual increase from 1.2x to 3x as the ratio grows. Cap at a maximum (e.g., 5x during extreme events). Update surge multipliers every 1-2 minutes per zone. Store in Redis for fast lookup. The pricing service reads the surge multiplier for the pickup zone and applies it to the base fare. Display the surge multiplier to the rider before they confirm the trip. UX: show "prices are higher due to increased demand" with the multiplier. Some riders will wait for surge to decrease; others will pay the premium. Both outcomes help balance supply and demand.
ETA Calculation
ETA accuracy directly affects user trust. Components: (1) Driver to pickup ETA — distance and traffic conditions between the driver current location and the pickup point. Use a routing service (Google Maps Directions API, self-hosted OSRM) for turn-by-turn routing with real-time traffic. For the initial estimate (before driver assignment), use a pre-computed travel time matrix: divide the city into geohash cells, maintain average travel times between pairs of cells, updated hourly from historical GPS traces. (2) Trip duration ETA — estimated travel time from pickup to destination. Same routing service, same traffic considerations. (3) Dynamic updates — after driver assignment, recalculate ETA every 30 seconds using the driver real-time GPS position and current traffic. Display a countdown on the rider app. ML model: train a gradient boosted tree (XGBoost, LightGBM) on features: distance, time of day, day of week, current traffic conditions (congestion index per road segment), weather, and special events. Historical trip data provides training labels (actual trip duration). The model improves accuracy over simple routing APIs by learning city-specific patterns (school zones slow at 3 PM, highway construction adds 10 minutes on weekdays).
Payment and Trip Settlement
Payment flow: (1) Upfront pricing — before the trip, calculate the estimated fare based on route distance, estimated duration, base fare, and surge multiplier. The rider sees and confirms the price. (2) Payment hold — when the rider confirms, place a hold on their payment method for the estimated amount (plus a buffer for route deviations). Use the payment gateway pre-authorization API. (3) Trip completion — calculate the final fare based on actual distance and time. If the actual fare is less than the upfront price (driver took a shorter route), charge the upfront price (the rider agreed to it). If significantly more (major detour), cap at a reasonable premium and investigate. (4) Settlement — charge the rider (capture the pre-authorized payment), deduct the platform commission (20-30%), and credit the driver balance. Driver payouts are batched daily or weekly. (5) Tips — the rider can add a tip after the trip. Tips go 100% to the driver. Payment service: use Stripe or Adyen as the payment gateway. Store payment methods tokenized (never store raw card numbers). Handle failed payments with retry logic and fallback payment methods.
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How does the driver matching algorithm work in a ride-sharing app?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”When a rider requests a ride: (1) Geographic search — query Redis GEOSEARCH for available drivers within 3-5 km of the pickup location. (2) ETA calculation — for each candidate, estimate arrival time using a routing API or pre-computed travel time matrix. (3) Ranking — score candidates by ETA (closest first), driver rating, acceptance rate, and vehicle type match. (4) Offer — send the request to top 3-5 candidates simultaneously with a 15-30 second acceptance window. The first to accept gets the trip. Parallel offering reduces matching time from potentially 90 seconds (3 sequential 30-second timeouts) to under 15 seconds. For high-demand areas, batch multiple ride requests and solve the optimal global assignment using the Hungarian algorithm, minimizing total wait time across all pending riders.”}},{“@type”:”Question”,”name”:”How does surge pricing work technically?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Divide the city into hexagonal zones (H3 geospatial indexing). For each zone every 1-2 minutes, compute: supply (available drivers) and demand (ride requests in the last 5 minutes). Surge multiplier = f(demand/supply). When demand is 3x supply, the multiplier might be 2.0x. The function is typically a step function: no surge when ratio < 1.2, gradual increase from 1.2x to cap (e.g., 5x). Store multipliers in Redis per zone. The pricing service reads the surge for the pickup zone and applies it to the base fare. Display to the rider before confirmation. Purpose: attract drivers to high-demand areas (higher earnings), reduce demand from price-sensitive riders, and balance supply/demand in real-time. The surge is zone-specific and time-limited."}},{"@type":"Question","name":"How do you track 1 million driver locations in real-time?","acceptedAnswer":{"@type":"Answer","text":"Each active driver sends GPS coordinates every 3-5 seconds. With 1 million drivers: 200K-333K updates/sec. Real-time index: Redis GEO data type. GEOADD drivers longitude latitude driver_id for updates. GEOSEARCH for proximity queries. Redis handles this throughput easily on a single instance. For higher scale, shard by geographic region. Location history: stream updates to Kafka, store in a time-series database (TimescaleDB, InfluxDB) for ETA model training, route optimization, and fraud detection. The real-time index is separate from the historical store. Rider updates: once matched, the rider app receives driver location via WebSocket every 3-5 seconds. The server subscribes to the matched driver location stream and pushes updates."}},{"@type":"Question","name":"How is ETA calculated accurately in a ride-sharing app?","acceptedAnswer":{"@type":"Answer","text":"ETA has three components: driver-to-pickup time, and trip duration (pickup to destination). For driver-to-pickup: use a routing service (Google Maps, OSRM) with real-time traffic. For the initial estimate before driver assignment, use a pre-computed travel time matrix (geohash cell to cell, updated hourly from historical GPS traces). For trip duration: same routing service with current traffic conditions. ML improvement: train a gradient boosted tree on features — distance, time of day, day of week, traffic congestion index, weather, special events. Historical trip data provides labels (actual duration). The model learns city-specific patterns (school zones at 3 PM, construction on specific routes). Display a range (25-35 minutes) rather than a point estimate to manage expectations. After driver assignment, recalculate every 30 seconds using real-time driver GPS position."}}]}