Skip to content
Design Uber/Lyft — Ride-Hailing System Architecture

Design Uber/Lyft — Ride-Hailing System Architecture

DodaTech Updated Jun 15, 2026 5 min read

Designing a ride-hailing service like Uber or Lyft tests your ability to build real-time location-based systems. You need driver matching, route optimization, surge pricing, and ETA calculation — all at global scale.

What You’ll Learn

You’ll master GPS polling vs WebSocket strategies, driver matching algorithms, surge pricing models, ETA calculation using routing engines, and QuadTree geospatial indexing.

Why This Problem Matters

Uber processes 25 million trips daily across 10,000+ cities. The architecture combines real-time streaming, geospatial indexing, marketplace economics, and machine learning. At DodaTech, similar geolocation patterns appear in Doda Browser’s location-aware features.

Architecture

    flowchart TB
  User[User App] -->|Request ride| LB[Load Balancer]
  Driver[Driver App] -->|GPS updates| LB
  LB --> API[API Gateway]
  API --> DM[Driver Matching Service]
  API --> Pricing[Surge Pricing Service]
  API --> ETA[ETA Service]
  DM --> Quad{QuadTree Index}
  Quad --> Redis[Redis Cache]
  DM --> Trips[(Trips DB)]
  Pricing --> Analytics[(Analytics)]
  ETA --> Maps[Maps/Routing API]
  

Location Tracking

Two approaches for sending GPS data:

MethodFrequencyBatteryUse Case
HTTP PollingEvery 3-5 secondsModerateWhen connection is unreliable
WebSocketContinuous streamLowerReal-time tracking

Uber sends GPS pings every 4 seconds via WebSocket during active trips, every 30 seconds when idle.

# GPS update handler (simplified)
async def handle_gps_update(driver_id: str, lat: float, lon: float):
    # Update QuadTree index
    quad_tree.update(driver_id, lat, lon)
    # Cache in Redis with expiration
    await redis.setex(
        f"driver:location:{driver_id}",
        30,  # TTL 30 seconds
        f"{lat},{lon}"
    )
    # Calculate new ETA for active rides
    active_ride = await get_active_ride(driver_id)
    if active_ride:
        eta = await calculate_eta(lat, lon, active_ride.pickup_lat, active_ride.pickup_lon)
        await notify_user(active_ride.user_id, eta)

QuadTree for Geospatial Indexing

Finding nearby drivers requires efficient spatial search. A QuadTree divides the map into quadrants:

class QuadTree:
    def __init__(self, bounds: tuple, capacity: int = 10):
        self.bounds = bounds  # (min_lat, max_lat, min_lon, max_lon)
        self.capacity = capacity
        self.drivers = []
        self.divided = False

    def search(self, bounds: tuple) -> list:
        """Return drivers within the given bounds."""
        if not self.intersects(bounds):
            return []
        result = [d for d in self.drivers if d.within(bounds)]
        if self.divided:
            for child in self.children:
                result.extend(child.search(bounds))
        return result

For each ride request, query a 5km x 5km quadrant around the pickup point. Expand radius if too few drivers.

Driver Matching

  1. Rider requests ride at location (lat, lon)
  2. Query QuadTree for drivers within 5km
  3. Filter out drivers who are offline, already matched, or not accepting rides
  4. Calculate ETA for each candidate
  5. Send ride request to top 3-5 nearest drivers simultaneously
  6. First driver to accept gets the match
  7. Notify remaining drivers “ride no longer available”

ETA Calculation

Use a routing engine (like OSRM, Google Maps API) to compute:

  • Pickup ETA: Time from current driver location to pickup point
  • Dropoff ETA: Time from pickup to destination
async def calculate_eta(from_lat, from_lon, to_lat, to_lon):
    # Returns duration in seconds
    response = await routing_service.route(
        from_lat, from_lon, to_lat, to_lon,
        profile="driving"
    )
    return response["duration"]

Surge Pricing

Surge pricing balances supply and demand:

surge_multiplier = base_price * max(1.0, demand / supply * sensitivity_factor)
  • Demand: Ride requests in the last 5 minutes in a geographic zone
  • Supply: Available drivers in the same zone
  • Sensitivity: Configurable per city (usually 1.5-2.0)

Common Mistakes

1. Polling GPS Too Frequently

Every 1-second GPS drains the phone battery in under 2 hours. Use adaptive polling: faster during active trips, slower when idle.

2. Not Filtering GPS Noise

GPS accuracy varies (5-50m). Apply Kalman filters or simple averaging to prevent driver positions from jumping.

3. Sending Ride Requests to All Nearby Drivers

Blasting every nearby driver creates bad UX — 20 drivers decline before one accepts. Use sequential targeting or a small batch.

4. Recalculating ETA from Scratch Each Time

Cache route results. The driver’s position changes continuously, but the route only needs recalculation every 10-15 seconds.

5. Ignoring Map Matching

GPS points often land off-road. Snap to road segments using map matching (Hidden Markov Model) for accurate ETAs.

6. Single Region for Surge Pricing

Large cities need micro-zones (500m x 500m). Surge in one neighborhood ≠ surge across the entire city.

7. Not Handling Driver Cancellation

Drivers may cancel after accepting. Implement a “re-match” flow that immediately finds the next best driver.

Practice Questions

1. How do you find nearby drivers efficiently?

Use a QuadTree or geohash spatial index. Don’t scan all drivers.

2. Why not broadcast a ride to every nearby driver?

It creates a poor experience — drivers see a ride and it’s gone. Use sequential or batched matching.

3. What’s the difference between pickup ETA and dropoff ETA?

Pickup ETA = driver location → pickup point. Dropoff ETA = pickup → destination (often pre-computed).

4. How does surge pricing work?

Increases prices when demand exceeds supply in a geographic zone. Encourages more drivers to enter the zone.

5. Challenge: Implement ride cancellation flow.

Handle a rider canceling within 5 minutes (no fee) vs after 5 minutes (fee). Notify the driver. If the driver has already arrived, charge the full fare.

Mini Project: Driver Location Server

Build a WebSocket server that:

  1. Accepts driver GPS coordinates (simulate with a script)
  2. Stores latest location in Redis with 30s TTL
  3. Exposes a REST endpoint: GET /nearby?lat=...&lon=...&radius=...
  4. Returns drivers within the radius using a simple grid index
  5. Simulate 100 drivers moving randomly and query nearby results

What’s Next

Congratulations on completing this Uber design! Here’s where to go from here:

  • Practice daily — Design one microservice per day
  • Build a project — Implement a matching algorithm
  • Explore related topics — Geospatial databases, real-time streaming
  • Join the community — Share your system designs and get feedback

Remember: every expert was once a beginner. Keep designing!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro