Skip to content

๐Ÿš— System Design: Uber / Ride-Sharing โ€‹

IntermediateIntermediate1โ€“3 yrs experience Builds on the foundations. Expect trade-off discussions, light math, and short code snippets.Prerequisites: Solid grasp of networking, databases, and at least one web framework.

Location-based, real-time matching at global scale.


Step 1: Requirements โ€‹

Functional โ€‹

  • Rider requests a ride
  • Match rider with nearby driver
  • Real-time location tracking of driver
  • Dynamic pricing (surge pricing)
  • ETA calculation
  • Route navigation

Non-Functional โ€‹

  • 10M trips/day, 5M active drivers
  • Location updates every 4 seconds
  • Match should happen in < 3 seconds
  • 99.99% uptime

Step 2: Capacity โ€‹

Location updates:
  5M active drivers ร— every 4 sec = 1.25M location writes/sec!

Matching requests:
  10M trips/day = ~115 trip requests/sec

Step 3: Core Problem โ€” Geospatial Matching โ€‹

How do you find drivers within 2km of a rider efficiently?

NAIVE approach:
  SELECT * FROM drivers WHERE lat BETWEEN ? AND ? AND lng BETWEEN ? AND ?
  โŒ Slow, no spatial indexing

SOLUTION: Geohash (or S2 cells like Uber uses)
  Divide the world into a grid of cells
  Each cell has a hash string: "9q8yy" = San Francisco area
  Encode lat/lng โ†’ geohash prefix

  Driver at (37.77, -122.41) โ†’ geohash "9q8yy"

  Find nearby drivers:
  1. Get geohash of rider location
  2. Find 8 neighboring cells
  3. Retrieve all drivers in those cells

  โœ… O(1) lookup from Redis!

Step 4: Architecture โ€‹

Example: Location Service updating Redis โ€‹

javascript
import Redis from "ioredis";

const redis = new Redis();

async function updateDriverLocation(driverId, lat, lng) {
  // Add or update the driver's location in the geospatial index
  // The 'driver_locations' key holds the geospatial data
  await redis.geoadd("driver_locations", lng, lat, driverId);

  // Optionally set an expiry if a driver hasn't pinged recently
  // This requires a separate ZSET or TTL key per driver in standard Redis
  await redis.setex(`driver_status:${driverId}`, 10, "ONLINE");

  console.log(`Updated location for driver ${driverId}`);
}

Step 5: Location Update Flow โ€‹

Every 4 seconds:
  Driver App โ”€โ”€โ–บ Location Service โ”€โ”€โ–บ Kafka
                                         โ”‚
                         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ–ผ
                  Location Consumer
                         โ”‚
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ–ผ               โ–ผ
          Redis (current      Cassandra (historical
          location +          location trail for
          geohash index)      route/analytics)

Step 6: Matching Flow โ€‹

Example: Trip Matching Service querying Redis โ€‹

javascript
import Redis from "ioredis";

const redis = new Redis();

async function findNearbyDrivers(riderLat, riderLng, radiusKm = 2) {
  // Query Redis for drivers within the specified radius
  // Returns array of arrays: [[driverId, distance], ...]
  const nearbyDrivers = await redis.geosearch(
    "driver_locations",
    "FROMLONLAT",
    riderLng,
    riderLat,
    "BYRADIUS",
    radiusKm,
    "km",
    "WITHDIST", // Include distance in response
    "ASC", // Sort nearest first
    "COUNT",
    10 // Limit to top 10 closest
  );

  const availableDrivers = [];

  for (const [driverId, distance] of nearbyDrivers) {
    // Check if the closer drivers are actually available/online
    const status = await redis.get(`driver_status:${driverId}`);
    if (status === "ONLINE") {
      availableDrivers.push({ driverId, distance });
    }
  }

  return availableDrivers;
}

Step 7: Surge Pricing โ€‹

Example: Calculating Surge Pricing โ€‹

javascript
import Redis from "ioredis";

const redis = new Redis();

// This would typically run in a stream processor or a cron job every minute
async function calculateSurgeMultiplier(geohashCell) {
  // In reality, these metrics might come from a time-series DB or stream aggregation
  const demandCount = parseInt((await redis.get(`demand:${geohashCell}`)) || 0);
  const supplyCount = parseInt((await redis.get(`supply:${geohashCell}`)) || 1); // Avoid div by 0

  const ratio = demandCount / supplyCount;
  let multiplier = 1.0;

  if (ratio > 3.0)
    multiplier = 2.0; // 2.0x surge
  else if (ratio > 2.0)
    multiplier = 1.5; // 1.5x surge
  else if (ratio > 1.2) multiplier = 1.2; // 1.2x surge

  // Store the new multiplier in Redis for quick access by pricing services
  await redis.set(`surge_multiplier:${geohashCell}`, multiplier);

  // Reset demand counters for the next window
  await redis.set(`demand:${geohashCell}`, 0);

  return multiplier;
}

Step 8: Trip Lifecycle โ€‹

Example: Trip State Machine โ€‹

javascript
class TripStateMachine {
  constructor(tripId) {
    this.tripId = tripId;
    this.state = "REQUESTED";
  }

  transition(event) {
    switch (this.state) {
      case "REQUESTED":
        if (event === "DRIVER_ACCEPTED") this.state = "ACCEPTED";
        if (event === "RIDER_CANCELLED") this.state = "CANCELLED";
        break;
      case "ACCEPTED":
        if (event === "DRIVER_MOVING") this.state = "DRIVER_EN_ROUTE";
        if (event === "DRIVER_CANCELLED") this.state = "REQUESTED"; // Rematch
        break;
      case "DRIVER_EN_ROUTE":
        if (event === "DRIVER_ARRIVED") this.state = "ARRIVED";
        break;
      case "ARRIVED":
        if (event === "TRIP_STARTED") this.state = "IN_TRIP";
        break;
      case "IN_TRIP":
        if (event === "TRIP_ENDED") this.state = "COMPLETED";
        break;
    }
    console.log(`Trip ${this.tripId} transitioned to ${this.state}`);
  }
}

const trip = new TripStateMachine("trip_123");
trip.transition("DRIVER_ACCEPTED"); // ACCEPTED
trip.transition("DRIVER_MOVING"); // DRIVER_EN_ROUTE

๐Ÿ“Š Summary โ€‹

ComponentTechnology
Location UpdatesKafka + Redis GeoHash
Geospatial IndexRedis Geo (S2 Cells internally)
Driver โ†” App commsWebSockets
Historical tripsCassandra
Trip state machineRedis + PostgreSQL
Route calculationGoogle Maps API / OSRM
Surge pricingStream processing (Flink/Spark)

Key insight: The geospatial indexing with geohash/S2 cells is the core performance trick. Without it, matching would require scanning millions of driver locations.

Released under the ISC License.