System Design Problem

Design a Ride-Hailing Service (Uber)

Commonly Asked By:UberLyftGrabDidiGoogle

Interview Setup

Interview Prompt

Design a ride hailing platform like Uber. Riders request trips, nearby drivers are matched, drivers navigate to pickup and dropoff. Support real time ETAs, dynamic pricing (surge), and trip lifecycle from request to payment.

Clarifying Questions (ask before designing)

QuestionWhy it matters
What scale is expected per city in terms of riders, drivers, and concurrent active trips?A metropolitan scale like New York City requires handling 100K concurrent trips and 50K online drivers, which directly determines location update throughput and matching service QPS.
What matching strategy should we adopt: immediate greedy nearest driver assignment or batch optimized dispatch?Greedy nearest driver matching typically requires logarithmic index navigation followed by candidate processing. Batch matching using the Hungarian or min-cost bipartite flow algorithm provides global optimization across candidates but introduces 2 to 5 seconds of buffering latency, making it useful for scheduled rides or dense event exits.
How should surge pricing be presented: as a dynamic real time multiplier or an upfront guaranteed fare quote?Upfront pricing requires forecasting supply and demand through the estimated dropoff time, whereas a dynamic multiplier is simpler to compute but creates unpredictable fare surges for riders.
How should trip state consistency be maintained if a driver application crashes mid-trip?The state machine must remain server authoritative, allowing clients to re-synchronize upon reconnecting through idempotent state transitions.

Scope

In scope

  • Driver location ingestion and geospatial indexing
  • Supply and demand matching and dispatch
  • ETA calculation for pickup and trip duration
  • Dynamic surge pricing based on localized supply and demand ratios
  • Trip state machine management and payment trigger integration

Out of scope (state explicitly)

  • Payment provider internals, settlement ledger design, and provider-specific reconciliation
  • In-app navigation map rendering
  • Driver onboarding and background checks
  • Food delivery multi-stop orders

Functional Requirements

Start by asking your interviewer about rider booking, driver matching, real time tracking, and fare estimation. Surge pricing and payments are typical follow-ups once the core match flow is clear.

  • Rider: Request a ride by specifying pickup and dropoff locations
  • Driver: Toggle online or offline availability, accept or decline ride offers, and navigate to pickup points
  • Matching: Match riders with nearby available drivers in real time
  • ETA Calculation: Provide estimated arrival times for pickup and total trip duration
  • Dynamic Pricing: Adjust fares dynamically through surge multipliers based on localized supply and demand
  • Real-time Tracking: Stream live GPS locations simultaneously to both rider and driver maps
  • Trip Lifecycle: Manage the complete state progression through request, match, pickup, in-trip transit, dropoff, payment, and rating
  • Payment Integration: Trigger rider charge and driver payout workflows under the platform commission model. Payment provider internals and settlement ledger design are covered separately.
  • Ratings and Reviews: Enable mutual ratings between riders and drivers following trip completion
  • Ride History: Allow riders and drivers to review past routes, trip summaries, and itemized receipts

Non-Functional Requirements

Interviewers focus heavily on matching latency and location update frequency. The geospatial index is the architectural centerpiece, so establish its design early before drawing service boxes.

  • Low Latency: Present the initial dispatch offer to a driver within 5 seconds
  • High Availability: Achieve 99.99% uptime because service outages leave passengers and drivers stranded
  • Real-Time Streaming: Ingest GPS coordinates every 3 to 5 seconds from all active online drivers
  • Massive Scalability: Support 100M+ registered riders, 5M+ registered drivers, and 20M+ completed trips daily
  • Strict Matching Consistency: Enforce atomic one-to-one driver assignment so two riders cannot claim the same driver
  • Geographic Distribution: Partition infrastructure cleanly across multiple cities and regional markets
  • Fault Tolerance: Ensure matched trip state is durably persisted and cannot be lost during process restarts

Capacity Estimations

Calculate throughput and storage requirements before selecting a geospatial indexing strategy. Concurrent driver counts, location update frequency, and index memory footprints determine whether QuadTree, S2, or H3 best fits the architecture.

MetricCalculationValue
Active drivers at any timeGiven (assumption documented in value)2M
Location updates / sec2M ÷ 4s500K
Rides / dayGiven (assumption documented in value)20M
Rides / sec20M ÷ 86400~231 (peak 1,000)
Location update sizeGiven (assumption documented in value)100 bytes
Raw location telemetry / day500K × 100B × 86400~4.3 TB

Architecture Diagram

In an interview, draw the core dispatch flow first: rider request, geospatial driver lookup, atomic assignment, and live tracking.

Walk through the architecture along the trip lifecycle. Drivers stream GPS updates into an in memory geospatial index over persistent WebSocket connections. When a rider requests a trip, the Matching Service queries nearby available drivers from the geospatial index and assigns one atomically using a short lived Redis contention lock followed by an authoritative PostgreSQL conditional update. The service also coordinates live location streaming between rider and driver. ETA remains a bounded synchronous dependency for candidate ranking, so cached route results and routing fallbacks protect the low latency dispatch path. Surge pricing runs asynchronously and does not block assignment.

Loading...

Component Deep Dives

Location Service: Handling 500K Location Updates/Sec

Half a million location updates per second require a purpose built geospatial index rather than relational database scans.

Ingestion Architecture: Driver mobile applications transmit GPS coordinates every 4 seconds over persistent WebSocket connections. Each WebSocket connection receives a server assigned session ID, which is registered in Redis before location updates begin. Each update includes that session ID, a monotonic sequence number, and a timestamp. The Location Service rejects updates from stale sessions or older sequence values, then updates the in memory geospatial index. Concurrently, it publishes telemetry records to the Kafka driver-location topic for downstream analytics, trip tracking, and speed profiling.

Geospatial Indexing Alternatives:

  • GeoHash with Redis: Encodes latitude and longitude into hierarchical string prefixes where proximate points share common prefixes. While straightforward, Redis single-threaded write bottlenecks make sustaining 500K location updates per second difficult without extensive manual sharding.
  • QuadTree: Recursively partitions two-dimensional space into four quadrants where each leaf node bounds at most ~100 drivers. The driver payload alone would require about 128 MB at 64 bytes per driver, before accounting for tree nodes and other index overhead. The structure can then be naturally sharded along metropolitan boundaries.
  • Google S2 Geometry: Projects the spherical Earth onto the faces of an enclosing cube using hierarchical Hilbert curve space-filling cells across 30 levels, with each cell referenced by a 64-bit integer identifier.
  • Uber H3 Hexagonal Grid: H3 provides 16 hierarchical resolutions. Resolution 9 has approximately 0.1 km² nominal cell areas with roughly 174-meter edges. Its hexagonal layout gives more uniform local neighborhoods than a square grid, which makes radial k-ring expansion predictable. For a fixed k, neighbor expansion is bounded, while filtering remains proportional to the returned driver set.

Matching Service: The Core Algorithm

Matching combines real time geospatial candidate retrieval with atomic assignment locks to prevent double booking drivers.

  1. The rider initiates a booking request specifying pickup and dropoff coordinates.
  2. The engine starts with a bounded search radius and expands it as needed, up to a configured maximum such as 5 km. At resolution 9, the service converts each radius into the required k-ring and then applies exact distance filtering rather than assuming that a single ring covers the full area.
  3. The service filters candidates by status, vehicle category, and minimum rating, then ranks them using road network ETA, traffic conditions, and driver acceptance scores.
  4. The system pushes a dispatch offer to the top-ranked candidate over WebSocket or push notification, initiating a 15-second response countdown.
  5. If the driver accepts, the service performs an atomic state transition and assigns the driver. If the driver declines or times out, the engine advances to the next candidate or expands the search radius.

Batch matching fallback: When sequential k-ring expansion fails after three attempts in high contention areas, the system holds incoming requests for a 5 second collection window and executes min-cost bipartite matching across drivers and pending requests in the H3 cell, substantially improving fulfillment rates without blocking standard dispatches.

Assignment Concurrency: To reduce contention when two riders target the same driver, the service acquires a short lived Redis lock. The lock is not the source of truth. Final ownership is established by the PostgreSQL compare and set plus the unique active driver constraint:

REDIS
SET driver:lock:drv_9821a trip_4b2190e NX EX 30

Pricing Service: Surge Pricing

Surge pricing dynamically balances localized supply and demand while operating asynchronously off the critical dispatch path.

The system partitions metropolitan areas into H3 hexagonal clusters (typically resolution 7). Within each cell, the Pricing Service tracks ride requests as demand and available drivers as supply over rolling time windows. When the demand-to-supply ratio rises above 1.5, a dynamic surge multiplier engages up to a configured ceiling of 8x. Multipliers are recalculated every 30 seconds and cached in Redis.

Fare Calculation Formula: The total fare is computed by taking base fare, adding elapsed duration multiplied by per-minute rates, distance multiplied by per-mile rates, applicable tolls, and booking fees, and then applying the localized surge multiplier, floored at the minimum ride fare.

Map and Route Service for ETA

Estimated time of arrival feeds candidate ranking and rider tracking, relying on precomputed routing graphs and cached segment traversals where possible.

The routing architecture operates independently from pricing calculations. Matching and Pricing services both query its APIs, but cached lookups ensure routing computations do not impede dispatch latency.

  • Routing Engine: Employs Open Source Routing Machine (OSRM), Valhalla, or custom contraction hierarchies over OpenStreetMap road networks.
  • Pickup ETA: Determines driving time from candidate driver coordinates to the rider pickup point using road network topology rather than Euclidean distances.
  • Real-Time Traffic Adjustments: Aggregates driver speed telemetry from Kafka across road segments in Apache Flink to dynamically update segment speed multipliers.
  • Trip ETA: Calculates travel duration from pickup to dropoff accounting for live congestion, caching origin-destination cell pairs with a 60-second time to live.

Ride Service: Trip State Machine

The trip state machine persists authoritative transitions so matched rides survive service restarts and client network disconnects.

The service orchestrates transitions across REQUESTED, MATCHED, DRIVER_EN_ROUTE, ARRIVED, IN_TRIP, COMPLETED, and PAYMENT_DONE states, with cancellation paths supported from early phases under applicable cancellation fee policies. Driver assignment is stored with the trip transition so the durable record remains the authority. Every state transition executes as an atomic PostgreSQL transaction with optimistic locking and records its Kafka event in the same transaction through the outbox. The outbox publisher delivers those events to the trip-events topic, and consumers process them idempotently. The PAYMENT_DONE transition is driven by a confirmed payment event, and client applications always resynchronize against the server authoritative state after reconnecting.

Loading...

Payment Service

Payment integration handles post trip charging and driver payout workflows while the payment provider internals, settlement ledger, and provider-specific reconciliation remain separate. Idempotency keys and transactional outbox patterns prevent duplicate charges during network retries and payment gateway handshakes.

  • Trip Completion Trigger: Activates asynchronously when the Ride Service emits a COMPLETED event.
  • Final Fare Auditing: Reconciles recorded GPS mileage, actual elapsed trip duration, dynamic surge multiplier, bridge tolls, and booking fees.
  • Cardholder Processing: Submits charges to payment service providers (PSPs) using an operation scoped idempotency key such as trip_id plus charge attempt to prevent double billing while allowing separate refund or payout operations.
  • Driver Disbursement: Aggregates driver net earnings after platform commission and schedules weekly automated clearing house (ACH) payouts.
  • Fare Splitting: Divides trip totals across multiple participating riders when split fare requests are authorized.

Notification Service

Channel adapters isolate provider-specific dispatch logic across Apple Push Notification service (APNs), Firebase Cloud Messaging (FCM), and SMS fallbacks.

  • Lifecycle Notifications: Consumes Kafka trip-events and delivers push alerts for driver arrival, trip initiation, and payment receipts.
  • Continuous Tracking Stream: Active vehicle movement updates during DRIVER_EN_ROUTE and IN_TRIP flow over persistent WebSocket channels rather than push notifications.

Event Bus Design (Kafka)

The distributed event bus decouples core producers from downstream consumers and buffers high volume telemetry spikes during peak hours.

YAML
topics:
  driver_location:
    partitions: 256 # Partition by driver_id to preserve per-driver ordering
    retention: 24h # Hot telemetry retention, archived to the data lake for long-term analytics
    replication_factor: 3
    min_insync_replicas: 2
    producers:
      - "Location Service (GPS ingest)"
    consumers:
      - "Trip Tracking Service"
      - "ETA Traffic Model (Flink speed aggregation)"
      - "Fraud and GPS Spoofing Detection"

  trip_events:
    partitions: 64 # Partition by trip_id
    retention: 7d # Compliance and dispute replay
    replication_factor: 3
    min_insync_replicas: 2
    producers:
      - "Ride Service through transactional outbox"
    consumers:
      - "Notification Service"
      - "Payment Service (triggered on COMPLETED)"
      - "Analytics Warehouse"
    idempotency_key: "(trip_id, transition, version)"

  payment_events:
    partitions: 32 # Partition by trip_id
    retention: 7d # Payment audit and replay
    replication_factor: 3
    min_insync_replicas: 2
    producers:
      - "Payment Service through transactional outbox (charge, refund, driver payout)"
    consumers:
      - "Ride Service (transitions COMPLETED to PAYMENT_DONE after confirmed settlement)"
      - "Finance General Ledger"

  push_notifications:
    partitions: 32
    producers:
      - "Notification Service"
    consumers:
      - "APNs and FCM dispatch workers"

execution_paths:
  sync_path: "Validate request, persist the trip row and outbox record in one PostgreSQL transaction, then return 201 Created"
  async_path: "Outbox publishing, location ingest streaming, surge recalculation, push notifications, and analytics pipelines"

reliability:
  event_delivery: "At-least-once delivery with idempotent consumers"
  outbox: "Prevents the PostgreSQL to Kafka dual-write gap for trip state events"

API Design

Domain Types

TYPESCRIPT
export interface GeoPoint {
  lat: number;
  lng: number;
}

export interface RequestRideRequest {
  pickup: GeoPoint;
  dropoff: GeoPoint;
  rideType: "UberX" | "Comfort" | "XL";
}

export interface AcceptRideRequest {
  acceptedAt: string;
}

export interface LocationUpdate {
  lat: number;
  lng: number;
  heading: number;
  speedMph: number;
  timestamp: string;
  sequence: number;
  sessionId: string;
}

Request Ride

Request ride, driver acceptance or decline, cancellation, live location update, and ride status endpoints cover the primary trip lifecycle. Rider and driver identities are derived from authenticated sessions rather than trusted from request bodies. Ride creation also accepts an idempotency key so network retries do not create duplicate trips.

HTTP
POST /api/v1/rides HTTP/1.1
Authorization: Bearer <rider_token>
Content-Type: application/json
Idempotency-Key: idemp_request_ride_4b2190e

{
  "pickup": {
    "lat": 37.7749,
    "lng": -122.4194,
    "address": "55 Music Concourse Dr, San Francisco, CA"
  },
  "dropoff": {
    "lat": 37.7849,
    "lng": -122.4094,
    "address": "100 Market St, San Francisco, CA"
  },
  "ride_type": "UberX"
}

// HTTP/1.1 201 Created
{
  "trip_id": "trip_4b2190e",
  "status": "requested",
  "estimated_fare": {
    "min": 12.50,
    "max": 16.00,
    "surge": 1.20
  },
  "estimated_pickup_eta": "4 min"
}

Driver Accept or Decline

HTTP
POST /api/v1/rides/trip_4b2190e/accept HTTP/1.1
Authorization: Bearer <driver_token>
Content-Type: application/json
Idempotency-Key: idemp_accept_trip_4b2190e

{
  "accepted_at": "2026-09-05T19:00:00Z"
}

POST /api/v1/rides/trip_4b2190e/decline HTTP/1.1
Authorization: Bearer <driver_token>
Content-Type: application/json

{
  "reason": "too_far"
}

Cancel Ride

HTTP
POST /api/v1/rides/trip_4b2190e/cancel HTTP/1.1
Authorization: Bearer <user_token>
Content-Type: application/json
Idempotency-Key: idemp_cancel_trip_4b2190e

{
  "reason": "rider_changed_plans"
}

Update Driver Location over WebSocket

JSON
{
  "type": "location_update",
  "lat": 37.7752,
  "lng": -122.4190,
  "heading": 45,
  "speed_mph": 25,
  "timestamp": "2026-09-05T19:00:00Z",
  "sequence": 981203
}

Get Ride Status

HTTP
GET /api/v1/rides/trip_4b2190e HTTP/1.1

// HTTP/1.1 200 OK
{
  "trip_id": "trip_4b2190e",
  "status": "in_trip",
  "driver": {
    "driver_id": "drv_9821a",
    "name": "Sarah T.",
    "vehicle": "Silver Toyota Prius (7XYZ123)",
    "rating": 4.92,
    "current_location": {
      "lat": 37.7780,
      "lng": -122.4150
    }
  },
  "pickup": {
    "lat": 37.7749,
    "lng": -122.4194
  },
  "dropoff": {
    "lat": 37.7849,
    "lng": -122.4094
  },
  "eta_to_dropoff": "12 min"
}

Common Error Responses

400 Bad Request: invalid input, missing required fields, or malformed JSON payload
401 Unauthorized: missing or invalid authentication token or API key
403 Forbidden: authenticated caller lacks required permissions for this resource
404 Not Found: requested resource ID does not exist
409 Conflict: duplicate write or version conflict, retry with a unique idempotency key
422 Unprocessable Entity: syntactically valid request failed semantic business validation
429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header
500 Internal Error: unexpected server failure, retry safely with an idempotency key
503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff
440 Login Timeout: WebSocket connection session expired, client reconnect is required

Data Model

Relational persistence supports financial auditability and transactional trip state. In memory structures support geospatial indexes, while the transactional outbox and Kafka provide durable event delivery.

PostgreSQL: Trips (ACID Required for Financial Auditing)

SQL
CREATE TABLE trips (
    trip_id          UUID PRIMARY KEY,
    rider_id         UUID NOT NULL,
    driver_id        UUID,
    status           VARCHAR(32) NOT NULL,
    ride_type        VARCHAR(20) NOT NULL,
    pickup_lat       DECIMAL(10,7) NOT NULL,
    pickup_lng       DECIMAL(10,7) NOT NULL,
    dropoff_lat      DECIMAL(10,7) NOT NULL,
    dropoff_lng      DECIMAL(10,7) NOT NULL,
    pickup_address   TEXT,
    dropoff_address  TEXT,
    surge_multiplier DECIMAL(3,2) DEFAULT 1.00,
    estimated_fare   DECIMAL(10,2),
    actual_fare      DECIMAL(10,2),
    distance_miles   DECIMAL(8,2),
    duration_minutes DECIMAL(8,2),
    started_at       TIMESTAMP WITH TIME ZONE,
    completed_at     TIMESTAMP WITH TIME ZONE,
    version          BIGINT NOT NULL DEFAULT 0,
    idempotency_key   VARCHAR(128) NOT NULL,
    created_at       TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE UNIQUE INDEX idx_trips_idempotency ON trips (rider_id, idempotency_key);
CREATE INDEX idx_trips_rider ON trips (rider_id, created_at DESC);
CREATE INDEX idx_trips_driver ON trips (driver_id, created_at DESC);
CREATE UNIQUE INDEX idx_active_driver_assignment ON trips (driver_id)
    WHERE driver_id IS NOT NULL
      AND status IN ('MATCHED', 'DRIVER_EN_ROUTE', 'ARRIVED', 'IN_TRIP');

PostgreSQL: Transactional Outbox

The Ride Service writes the trip state change and its outbox event in the same PostgreSQL transaction.

SQL
CREATE TABLE trip_outbox (
    event_id      UUID PRIMARY KEY,
    trip_id       UUID NOT NULL,
    event_type    VARCHAR(64) NOT NULL,
    version       BIGINT NOT NULL,
    payload       JSONB NOT NULL,
    created_at    TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    published_at  TIMESTAMP WITH TIME ZONE
);

A publisher retries unpublished rows until Kafka acknowledges the event. Downstream consumers remain idempotent because delivery is at least once.

Redis: Driver Availability and Spatial State

REDIS
# H3 cell membership for available drivers
SADD drivers:h3:r9:8928308280fffff "driver_9821"

# Driver real time telemetry state
HSET driver:state:driver_9821 status "available" trip_id "" h3_cell "8928308280fffff" lat 37.7749 lng -122.4194 heading 45 sequence 981203 updated_at 1710320000

# Surge pricing multiplier (TTL: 60s, refreshed every 30s)
SET surge:sf:8828308281fffff "1.5" EX 60

# Redis lock reduces assignment contention (TTL: 30s)
SET driver:lock:driver_9821 "trip_7102" NX EX 30

The location update script checks the server assigned session ID and sequence number before changing state. A new WebSocket connection registers a new session ID before updates begin, so delayed packets from an older connection are rejected. The script updates the driver's current coordinates even when the driver is busy, but only maintains H3 availability set membership while the driver status is available.

LUA
local active_session = redis.call('HGET', KEYS[3], 'session_id') or ''
if active_session ~= ARGV[5] then return 0 end

local current = tonumber(redis.call('HGET', KEYS[3], 'sequence') or '-1')
if tonumber(ARGV[3]) <= current then return 0 end

local old_cell = redis.call('HGET', KEYS[3], 'h3_cell') or ''
local status = redis.call('HGET', KEYS[3], 'status') or 'unavailable'

if status == 'available' then
  if old_cell ~= '' then
    redis.call('SREM', KEYS[1], ARGV[1])
  end
  redis.call('SADD', KEYS[2], ARGV[1])
end

redis.call('HSET', KEYS[3],
  'h3_cell', ARGV[2],
  'sequence', ARGV[3],
  'updated_at', ARGV[4],
  'lat', ARGV[6],
  'lng', ARGV[7],
  'heading', ARGV[8],
  'speed_mph', ARGV[9]
)
return 1

Kafka Topics and Message Schemas

YAML
driver_location:
  description: "High-throughput telemetry partitioned by driver_id"
  payload: "{ driver_id, lat, lng, heading, speed_mph, timestamp }"
trip_events:
  description: "Trip lifecycle state machine events"
  payload: "{ trip_id, rider_id, driver_id, previous_state, new_state, version, timestamp }"
payment_events:
  description: "Payment lifecycle events partitioned by trip_id"
  payload: "{ trip_id, rider_id, amount_cents, currency, status, idempotency_key }"
surge_updates:
  description: "Pricing multiplier per H3 cell refreshed every 30 seconds"
  payload: "{ city_id, h3_cell_id, multiplier, expires_at }"

Cassandra: Historical Location Trail

The Cassandra trail stores sampled trip associated locations needed for ride history, support, and dispute analysis. High volume raw driver telemetry remains on Kafka and is archived to the data lake rather than writing every online driver's ping to this table.

SQL
CREATE TABLE trip_location_trail (
    trip_id     UUID,
    timestamp   TIMESTAMP,
    lat         DECIMAL,
    lng         DECIMAL,
    speed       FLOAT,
    PRIMARY KEY (trip_id, timestamp)
) WITH CLUSTERING ORDER BY (timestamp ASC)
  AND default_time_to_live = 2592000; -- 30-day retention

Fault Tolerance

ConcernSolution
Matching service failureRetry matching while rider UI displays looking for driver status until timeout
Location service lagLocations older than 30 seconds are deprioritized, and drivers without telemetry for 60 seconds are marked unavailable and removed from active H3 availability sets
Payment failureRetry using an idempotency key with fallback to asynchronous post trip settlement
Driver app crashTrip state is persisted durably in PostgreSQL so drivers can reconnect and resume seamlessly
Network partitionClients cache the last confirmed trip state. They may queue idempotent outbound intents when the current state allows them, and the server remains authoritative when those intents are reconciled after reconnect
Double matchingA short lived Redis NX lock reduces assignment contention, while a durable PostgreSQL conditional update is the authoritative guard against double assignment

Handling Offline or Unresponsive Matched Drivers

Driver disconnection handling, telemetry staleness windows, and atomic assignment controls support high availability under network degradation.

  1. The Location Service detects missing driver heartbeats when no telemetry arrives for 60 seconds.
  2. The system marks the driver unavailable, removes the driver from the active H3 availability set, and schedules the affected trip for reassignment without introducing a new durable trip state.
  3. The Matching Service automatically re-enters dispatch for the rider with an elevated priority score.
  4. The rider application receives an immediate status update indicating that a new driver is being located.
  5. The system records the disconnect as a driver reliability signal for dispatch policies without treating a crash or network outage as a voluntary decline.

Additional Considerations

Surge pricing safeguards, route pooling, passenger safety features, and operational telemetry expand platform capability beyond the primary dispatch path.

Geofencing

  • Define virtual polygonal boundaries around airports, transit hubs, and sports arenas.
  • When a driver crosses into an airport geofence, the dispatch system automatically places them into a virtual first-in first-out queue.
  • When a rider requests a pickup within a designated geofence, special surcharge rules or pickup-zone instructions apply.
  • H3 cells quickly narrow the candidate area, after which exact point in polygon checks enforce the geofence boundary.

Ride Pooling Optimization

  • Match multiple riders traveling along overlapping trajectories to share vehicle capacity.
  • For each new booking, evaluate active trips to identify routes where detour additions remain under 5 minutes.
  • Route evaluation requires continuous spatiotemporal route comparisons across candidate combinations.
  • Riders receive discounted upfront fares while drivers earn compensation across the full extended route.

Passenger and Driver Safety Features

  • Share My Trip: Enables riders to stream live trip coordinates and driver details to emergency contacts.
  • Emergency Assistance: Provides a one-touch in-app trigger connecting directly to emergency responders with real time GPS telemetry.
  • Real-Time ID Verification: Prompts drivers periodically for biometric selfie validation before allowing them to accept dispatches.
  • Route Deviation Detection: Flags significant deviations from recommended navigation paths and alerts safety operations teams.

Analytics and Operational Monitoring

  • Real-time operations consoles display active rides, available supply density per city, and dynamic surge heatmaps.
  • Predictive machine learning pipelines forecast hourly demand per geographic sector and proactively distribute incentive bonuses to reposition drivers before demand spikes occur.

Related Problems and Architecture Concepts

Explore related system design problems and foundational architectural concepts that expand on patterns introduced in this design:

Interview Walkthrough

  • 25-minute cut

    Focus on foundational architecture unless staff level depth is explicitly requested.

    • Functional and non-functional requirements with dispatch flow overview (3 min)
    • Geospatial indexing for real time driver lookup (7 min)
    • WebSocket location streaming at 3 to 5 second cadence (6 min)
    • Surge pricing as an asynchronous supply and demand feedback loop (5 min)
    • ETA calculation as cached route lookups (4 min)
  • Clarify the dispatch lifecycle by distinguishing between rider booking, geospatial candidate search, atomic assignment, and live trip tracking as separate subsystems.
  • Detail geospatial indexing options such as GeoHash, S2, QuadTree, or H3 for candidate retrieval, referencing System Design Interview Patterns to structure the discussion around read focused location caching.
  • Cover driver location ingestion over WebSocket at adaptive frequencies, balancing GPS coordinate accuracy against client battery life and cellular bandwidth.
  • Isolate surge pricing as an asynchronous background calculation driven by supply and demand ratios rather than placing it synchronously on the critical dispatch path.
  • Explain ETA calculation using precomputed routing graphs and cached origin-destination matrices decoupled from the core matching loop.
  • Highlight common pitfalls, such as attempting global relational database queries across all online drivers, and emphasize why geographic partitioning by metropolitan market is mandatory.

Engineering Trade-offs

Geospatial Index: QuadTree vs GeoHash vs H3 vs S2

Interviewers frequently probe spatial index selection and location streaming protocols. Walk through the trade offs between GeoHash, S2, QuadTree, and H3, stating clearly why H3 fits city scale ride hailing.

H3 is a strong fit because its hierarchical hexagonal cells provide more uniform local neighborhoods and predictable neighbor expansion. This makes radial candidate searches easier to bound and reduces boundary artifacts compared with square grids. H3 supports hierarchical resolutions such as resolution 9 for driver matching and resolution 7 for surge aggregation, with compact 64 bit identifiers suitable for memory efficient Redis indexing. For a fixed k, the neighbor expansion is bounded, while candidate filtering remains proportional to the number of returned drivers.

Why WebSocket for Driver Location Instead of HTTP Polling

HTTP polling every 4 seconds creates substantial network overhead because each HTTP request carries headers of 500 to 1,000 bytes, generating roughly 15 to 30 GB per minute across 2M active drivers. In contrast, persistent WebSocket frames transmit compact binary or JSON payloads of approximately 100 bytes, reducing payload bandwidth to about 3 GB per minute. That represents roughly a 5 to 10 times reduction in per update payload overhead before accounting for connection setup and other HTTP costs. Persistent duplex connections also enable immediate server push delivery of ride offers without polling delays.

Matching Algorithm: Greedy Assignment vs Batch Optimization

Greedy dispatch assigns ride requests immediately to the nearest candidate driver, minimizing rider booking latency but yielding globally suboptimal fleet efficiency. Batch optimization gathers requests within a 5-second window and applies the Hungarian algorithm or min-cost bipartite flow to maximize global matching efficiency at the cost of slight dispatch latency. Production systems implement a hybrid strategy: low-density periods use greedy dispatch, while high density areas switch to 2-second batch optimization windows.

Why PostgreSQL for Trips Instead of Cassandra or DynamoDB

Trip management requires strict ACID transactional guarantees because financial balances, driver assignments, and state transitions must execute atomically. PostgreSQL delivers ACID transactions, complex relational queries, foreign key constraints, and row-level locking for concurrent updates. Cassandra lacks multi-table transactions and relational joins, while DynamoDB offers limited cross-partition transaction scopes and constrained querying flexibility.

Surge Pricing: Technical Implementation and Consumer Protections

From a technical standpoint, surge multipliers reflect localized supply-to-demand ratios per H3 cell capped at an upper ceiling of 8x. From an ethical standpoint, systems enforce automatic surge freezes during natural disasters or civil emergencies, provide riders with upfront transparent fare quotes before booking, and display estimated wait times for lower fares. Dynamic pricing serves as an economic regulator to incentivize idle drivers into high demand zones while moderating peak request volume.

Driver Location Updates: Accuracy vs Battery Consumption vs Bandwidth

Transmitting updates every 4 seconds strikes an optimal balance between positional accuracy (limiting coordinate drift to 10 to 40 meters), device battery drain (approximately 5% per hour), and cellular bandwidth (under 100 KB per hour). Advanced clients adopt adaptive reporting intervals: stationary vehicles report every 30 seconds, slow-moving traffic reports every 10 seconds, normal transit reports every 4 seconds, and active trips approaching pickup points report every 2 seconds.

💬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...