---
# === IDENTITY ===
id: software/system-design/ride-sharing-platform/2026
canonical_question: "How do I design a ride-sharing platform (Uber clone)?"
aliases:
  - "Design a ride-sharing service like Uber"
  - "Uber system design interview"
  - "Ride-hailing platform architecture"
  - "How to build an Uber clone"
  - "Lyft system design"
  - "Ride matching service architecture"
  - "Real-time ride dispatch system design"
entity_type: software_reference
domain: software > system-design > ride_sharing_platform
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.91
version: 1.0
first_published: 2026-02-23

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: null
  next_review: 2026-08-22
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "GPS accuracy is 3-10m in urban environments — never rely on raw GPS for precise pickup; use map-snapping"
  - "Driver location updates must flow through a message queue (Kafka/Pulsar) — direct DB writes at >10K drivers/sec will overwhelm any relational database"
  - "Surge pricing must be capped and transparent — several jurisdictions (NYC, EU) regulate maximum multipliers"
  - "Payment processing must be PCI-DSS compliant — never store raw card numbers; use tokenized payment providers (Stripe, Braintree)"
  - "WebSocket connections require sticky sessions or a connection registry — stateless load balancing will drop real-time updates"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Building a food/grocery delivery platform (not ride-sharing)"
    use_instead: "Food delivery system design — different matching logic (restaurant prep time, multi-order batching)"
  - condition: "Designing only the payment/billing subsystem"
    use_instead: "Payment processing system design — PCI compliance, idempotent charges, refund flows"
  - condition: "Building a carpooling/ride-pooling service with shared rides"
    use_instead: "Ride-pooling system design — requires route merging algorithms and detour optimization"

# === AGENT HINTS ===
inputs_needed:
  - key: scale
    question: "What is the expected scale (concurrent users)?"
    type: choice
    options: ["<1K (MVP)", "1K-100K (city-level)", "100K-1M (regional)", ">1M (global)"]
  - key: matching_priority
    question: "What is the primary matching priority?"
    type: choice
    options: ["Lowest wait time", "Shortest distance", "Driver rating", "Cost optimization"]
  - key: pricing_model
    question: "Which pricing model will you use?"
    type: choice
    options: ["Fixed fare", "Distance + time metered", "Dynamic/surge pricing", "Subscription-based"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/system-design/ride-sharing-platform/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/migrations/monolith-to-microservices/2026"
      label: "Monolith to Microservices Migration"
  solves: []
  alternative_to: []
  often_confused_with: []

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "H3: Uber's Hexagonal Hierarchical Spatial Index"
    author: Uber Engineering
    url: https://www.uber.com/en-US/blog/h3/
    type: technical_blog
    published: 2018-06-27
    reliability: authoritative
  - id: src2
    title: "Design a Ride-Sharing Service Like Uber"
    author: Hello Interview
    url: https://www.hellointerview.com/learn/system-design/problem-breakdowns/uber
    type: community_resource
    published: 2024-09-15
    reliability: high
  - id: src3
    title: "How Uber Finds Nearby Drivers at 1 Million Requests per Second"
    author: System Design Newsletter
    url: https://newsletter.systemdesign.one/p/how-does-uber-find-nearby-drivers
    type: technical_blog
    published: 2024-01-20
    reliability: high
  - id: src4
    title: "System Design of Uber App — Uber System Architecture"
    author: GeeksforGeeks
    url: https://www.geeksforgeeks.org/system-design/system-design-of-uber-app-uber-system-architecture/
    type: community_resource
    published: 2024-08-10
    reliability: moderate_high
  - id: src5
    title: "Uber System Design: Architecture and Scalability"
    author: Karan Pratap Singh
    url: https://www.karanpratapsingh.com/courses/system-design/uber
    type: community_resource
    published: 2024-03-01
    reliability: high
  - id: src6
    title: "Dynamic Pricing in Ridesharing Platforms"
    author: ACM SIGecom Exchanges
    url: https://dl.acm.org/doi/10.1145/2994501.2994505
    type: academic_paper
    published: 2016-12-01
    reliability: authoritative
  - id: src7
    title: "Fraud Detection in Mobility Services with Apache Kafka and Flink"
    author: Kai Waehner
    url: https://www.kai-waehner.de/blog/2025/04/28/fraud-detection-in-mobility-services-ride-hailing-food-delivery-with-data-streaming-using-apache-kafka-and-flink/
    type: technical_blog
    published: 2025-04-28
    reliability: high
---

# Ride-Sharing Platform System Design (Uber Clone)

## TL;DR

- **Bottom line**: A ride-sharing platform requires five core subsystems: real-time location tracking (WebSocket + Kafka), geospatial driver matching (H3 hexagonal index), dynamic surge pricing, trip management (state machine), and payment processing (async, idempotent).
- **Key tool/command**: `h3-js` library for hexagonal geospatial indexing — converts lat/lng to cell IDs for O(1) nearest-driver lookups instead of brute-force distance calculations.
- **Watch out for**: Storing driver locations in a relational DB — at scale, you need an in-memory geospatial index (Redis GEO or custom H3 grid) updated every 3-4 seconds per driver.
- **Works with**: Any cloud provider (AWS/GCP/Azure), any language stack; core patterns are language-agnostic.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- GPS accuracy is 3-10m in urban canyons — always map-snap coordinates to the road network before matching or ETA calculation
- Driver location updates at scale (>10K concurrent drivers) must go through a message queue (Kafka/Pulsar), not direct DB writes
- Surge pricing multipliers are regulated in multiple jurisdictions (NYC caps at 2x during emergencies, EU has transparency requirements) — always implement configurable caps
- Payment charges must be idempotent — network retries must not double-charge riders; use idempotency keys with your payment provider
- WebSocket-based real-time connections require sticky sessions or a connection registry service — stateless horizontal scaling will silently drop connections

## Quick Reference

| Component | Role | Technology Options | Scaling Strategy |
|---|---|---|---|
| API Gateway | Rate limiting, auth, request routing | Kong, AWS API Gateway, Envoy | Horizontal + edge caching |
| Rider Service | Ride requests, fare estimates, trip history | Node.js, Go, Java Spring | Stateless horizontal pods |
| Driver Service | Driver onboarding, availability, earnings | Go, Java, Kotlin | Stateless horizontal pods |
| Location Service | Ingest GPS pings, maintain driver positions | Go + Redis GEO, custom H3 grid | Sharded by geo-region |
| Matching Service | Pair riders with nearest available drivers | Go, Rust, Java | Sharded by H3 cell region |
| Trip Service | Trip state machine (requested -> matched -> in_progress -> completed) | Node.js, Go | Event-sourced with Kafka |
| Pricing Service | Fare calculation, surge pricing, promotions | Python, Go | Stateless; reads supply/demand from cache |
| Payment Service | Charge riders, pay drivers, handle refunds | Java, Node.js + Stripe/Braintree | Async processing with idempotency |
| Notification Service | Push notifications, SMS, email | Node.js, Go + Firebase/APNs/SNS | Fan-out via message queue |
| ETA Service | Estimated time of arrival, route optimization | Python, C++ + OSRM/Valhalla | Precomputed graph + ML model |
| WebSocket Gateway | Persistent connections for real-time updates | Node.js (Socket.io), Go (gorilla/websocket) | Sticky sessions + connection registry |
| Message Queue | Async event bus for all services | Apache Kafka, Apache Pulsar | Partitioned by driver_id or trip_id |
| Geospatial Index | Fast nearest-neighbor driver lookup | Redis GEO, H3 in-memory grid, PostGIS | Sharded by H3 resolution-3 cells |
| Analytics Pipeline | Trip data, driver metrics, business intelligence | Kafka -> Spark/Flink -> data warehouse | Batch + real-time (Lambda architecture) |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (29 lines)

```
START
├── Expected scale?
│   ├── <1K concurrent users (MVP)
│   │   ├── Use monolith with PostGIS for location queries
│   │   ├── Simple distance-based matching (SQL query)
# ... (see full script)
```

## Step-by-Step Guide

### 1. Define the data model and core entities

Design your database schema around four core entities: Users (riders + drivers), Vehicles, Trips, and Payments. Use PostgreSQL for transactional data and Redis for real-time state. [src2]

```sql
-- Core tables (PostgreSQL)
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    role VARCHAR(10) NOT NULL CHECK (role IN ('rider', 'driver')),
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    phone VARCHAR(20) UNIQUE NOT NULL,
    rating DECIMAL(3,2) DEFAULT 5.00,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE vehicles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    driver_id UUID REFERENCES users(id),
    make VARCHAR(100) NOT NULL,
    model VARCHAR(100) NOT NULL,
    year INT NOT NULL,
    license_plate VARCHAR(20) UNIQUE NOT NULL,
    vehicle_type VARCHAR(20) DEFAULT 'standard'
);

CREATE TABLE trips (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    rider_id UUID REFERENCES users(id),
    driver_id UUID REFERENCES users(id),
    status VARCHAR(20) NOT NULL DEFAULT 'requested',
    pickup_lat DOUBLE PRECISION NOT NULL,
    pickup_lng DOUBLE PRECISION NOT NULL,
    dropoff_lat DOUBLE PRECISION NOT NULL,
    dropoff_lng DOUBLE PRECISION NOT NULL,
    fare_cents INT,
    surge_multiplier DECIMAL(3,2) DEFAULT 1.00,
    requested_at TIMESTAMPTZ DEFAULT NOW(),
    matched_at TIMESTAMPTZ,
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ
);

CREATE INDEX idx_trips_status ON trips(status);
CREATE INDEX idx_trips_driver ON trips(driver_id) WHERE status = 'in_progress';
```

**Verify**: `SELECT count(*) FROM information_schema.tables WHERE table_name IN ('users', 'vehicles', 'trips');` -> expected: `3`

### 2. Implement the location ingestion pipeline

Drivers send GPS pings every 3-4 seconds over WebSocket. These flow through Kafka to the Location Service, which updates an in-memory geospatial index for real-time matching and writes to a time-series store for trip tracking. [src3]

```
Driver App --[WebSocket]--> WebSocket Gateway
    --[produce]--> Kafka (topic: driver.location)
    --[consume]--> Location Service
        ├── Hot path: Update Redis GEO / H3 in-memory index
        └── Cold path: Write to TimescaleDB for trip trace history
```

**Verify**: Send a test location ping and confirm Redis GEOSEARCH returns the driver within the expected radius.

### 3. Build the geospatial matching engine

Use Uber's H3 hexagonal grid to partition the map. When a ride request arrives, convert the pickup location to an H3 cell ID, then search that cell and its k-ring neighbors for available drivers. This reduces the search space from millions of drivers to dozens. [src1]

```python
# pip install h3==4.1.0
import h3

def find_nearby_drivers(pickup_lat, pickup_lng, driver_index, k=1):
    """
    Find available drivers near pickup using H3 hexagonal grid.

    Input:  pickup coordinates + driver_index (dict: h3_cell -> set of driver_ids)
    Output: list of driver_ids sorted by proximity
    """
    # Resolution 9 = ~174m edge length, good for city-level matching
    pickup_cell = h3.latlng_to_cell(pickup_lat, pickup_lng, 9)

    # Get the pickup cell + surrounding ring of cells
    search_cells = h3.grid_disk(pickup_cell, k)

    nearby_drivers = []
    for cell in search_cells:
        if cell in driver_index:
            nearby_drivers.extend(driver_index[cell])

    return nearby_drivers
```

**Verify**: `h3.latlng_to_cell(40.7128, -74.0060, 9)` returns a valid H3 index string (15 hex characters).

### 4. Implement the ride matching algorithm

Score candidate drivers using a weighted function of distance, ETA, rating, and acceptance rate. Send the ride offer to the top-ranked driver via WebSocket; if they decline or timeout (10-15s), cascade to the next. [src2]

```python
import math

def score_driver(driver, pickup_lat, pickup_lng):
    """
    Score a driver candidate for matching.
    Higher score = better match.

    Input:  driver dict with lat, lng, rating, acceptance_rate, heading
    Output: float score (0-100)
    """
    # Haversine distance in km
    dist = haversine(pickup_lat, pickup_lng, driver['lat'], driver['lng'])

    # Distance score: inversely proportional, max 5km considered
    distance_score = max(0, (5.0 - dist) / 5.0) * 40  # 40% weight

    # Rating score (1-5 scale)
    rating_score = (driver['rating'] / 5.0) * 30        # 30% weight

    # Acceptance rate score
    accept_score = driver['acceptance_rate'] * 20         # 20% weight

    # Heading bonus: driver already heading toward pickup
    heading_bonus = 10 if driver.get('heading_toward') else 0  # 10% weight

    return distance_score + rating_score + accept_score + heading_bonus

def haversine(lat1, lng1, lat2, lng2):
    """Calculate great-circle distance between two points in km."""
    R = 6371  # Earth radius in km
    dlat = math.radians(lat2 - lat1)
    dlng = math.radians(lng2 - lng1)
    a = (math.sin(dlat/2)**2 +
         math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
         math.sin(dlng/2)**2)
    return R * 2 * math.asin(math.sqrt(a))
```

**Verify**: `score_driver({'lat': 40.713, 'lng': -74.006, 'rating': 4.8, 'acceptance_rate': 0.92, 'heading_toward': True}, 40.7128, -74.0060)` returns a score > 80.

### 5. Build the surge pricing engine

Calculate surge multipliers per H3 cell by comparing demand (ride requests in the last N minutes) to supply (available drivers in the cell). Update every 30-60 seconds. [src6]

```python
def calculate_surge(cell_id, request_count, available_drivers,
                    base_threshold=0.7, max_multiplier=3.0):
    """
    Calculate surge pricing multiplier for an H3 cell.

    Input:  cell_id, request count (last 5 min), available driver count
    Output: surge multiplier (1.0 = no surge, up to max_multiplier)
    """
    if available_drivers == 0:
        return max_multiplier

    # Supply/demand ratio
    ratio = request_count / available_drivers

    if ratio <= base_threshold:
        return 1.0  # No surge — supply meets demand

    # Linear surge above threshold, capped
    surge = 1.0 + (ratio - base_threshold) * 1.5
    return min(round(surge, 2), max_multiplier)


# Example: 50 requests, 20 drivers -> ratio 2.5 -> surge 3.0 (capped)
# Example: 30 requests, 40 drivers -> ratio 0.75 -> surge 1.08
```

**Verify**: `calculate_surge("cell_abc", 10, 20)` -> `1.0` (supply exceeds demand), `calculate_surge("cell_abc", 50, 10)` -> `3.0` (capped at max).

### 6. Implement the trip state machine

Model each trip as a finite state machine: `requested -> matched -> driver_arriving -> in_progress -> completed` (with branches for `cancelled`, `no_drivers`). Use event sourcing via Kafka so every state transition is an immutable event. [src4]

```
Trip State Machine:
    REQUESTED ──[driver accepts]──> MATCHED
    REQUESTED ──[timeout/no drivers]──> NO_DRIVERS
    REQUESTED ──[rider cancels]──> CANCELLED
    MATCHED ──[driver arrives at pickup]──> DRIVER_ARRIVING
    MATCHED ──[driver cancels]──> REQUESTED (re-match)
    DRIVER_ARRIVING ──[rider picked up]──> IN_PROGRESS
    DRIVER_ARRIVING ──[rider cancels]──> CANCELLED (cancellation fee)
    IN_PROGRESS ──[arrived at destination]──> COMPLETED
    COMPLETED ──[payment processed]──> PAID
```

**Verify**: Confirm no valid transition exists from `COMPLETED` back to `IN_PROGRESS` — trips are append-only.

### 7. Set up the payment and billing pipeline

Process payments asynchronously after trip completion. Use idempotency keys to prevent double charges. Hold an authorization at ride request, then capture the final amount (distance-metered + surge) at completion. [src5]

```
Trip Completed Event (Kafka)
    --> Payment Service
        1. Calculate final fare: base + (distance_km * per_km) + (duration_min * per_min) * surge
        2. Capture pre-authorized amount (Stripe/Braintree)
        3. If capture fails: retry with exponential backoff (max 3 attempts)
        4. Emit PaymentCompleted event
        5. Driver payout: batch settlement (daily/weekly)
```

**Verify**: Submit two identical payment capture requests with the same idempotency key -> only one charge should appear.

## Code Examples
<!-- Keep inline examples <=15 lines. For longer scripts, extract to scripts/ subdirectory
     and link: "Full script: [name.ext](scripts/name.ext) (N lines)" -->

### Python: Geospatial Driver Matching with H3

```python
# Input:  rider pickup coordinates, dict of active drivers
# Output: ranked list of nearby driver IDs

# pip install h3==4.1.0 redis==5.0.0
import h3
import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def update_driver_location(driver_id: str, lat: float, lng: float):
    """Store driver location in Redis GEO + H3 index."""
    r.geoadd("drivers:geo", (lng, lat, driver_id))
    cell = h3.latlng_to_cell(lat, lng, 9)
    r.sadd(f"drivers:h3:{cell}", driver_id)
    r.set(f"drivers:cell:{driver_id}", cell, ex=30)  # TTL 30s

def match_rider(pickup_lat: float, pickup_lng: float, radius_km: float = 3.0):
    """Find nearest drivers using Redis GEO within radius."""
    results = r.geosearch(
        "drivers:geo", longitude=pickup_lng, latitude=pickup_lat,
        radius=radius_km, unit="km", sort="ASC", count=10
    )
    return [driver_id.decode() for driver_id in results]
```

### JavaScript/Node.js: WebSocket Driver Connection Handler

```javascript
// Input:  WebSocket connection from driver app
// Output: real-time location updates forwarded to Kafka

// npm install ws@8 kafkajs@2
const { Kafka } = require('kafkajs');
const WebSocket = require('ws');

const kafka = new Kafka({ brokers: ['kafka:9092'] });
const producer = kafka.producer();

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws, req) => {
  const driverId = req.headers['x-driver-id'];
  ws.on('message', async (data) => {
    const { lat, lng, timestamp } = JSON.parse(data);
    await producer.send({
      topic: 'driver.location',
      messages: [{ key: driverId, value: JSON.stringify({ lat, lng, timestamp }) }]
    });
  });
});
```

### Go: Surge Pricing Calculator

```go
// Input:  demand (request count), supply (driver count) for an H3 cell
// Output: surge multiplier (float64)

package pricing

import "math"

const (
    BaseThreshold = 0.7
    MaxMultiplier = 3.0
    SurgeSlope    = 1.5
)

func CalculateSurge(requests, drivers int) float64 {
    if drivers == 0 {
        return MaxMultiplier
    }
    ratio := float64(requests) / float64(drivers)
    if ratio <= BaseThreshold {
        return 1.0
    }
    surge := 1.0 + (ratio-BaseThreshold)*SurgeSlope
    return math.Min(math.Round(surge*100)/100, MaxMultiplier)
}
```

## Anti-Patterns

### Wrong: Polling all drivers in the database for every ride request

```sql
-- BAD — full table scan on every ride request, O(n) for n drivers
SELECT id, lat, lng,
       ST_Distance(location, ST_MakePoint(-74.006, 40.7128)) AS dist
FROM drivers
WHERE is_available = true
ORDER BY dist ASC
LIMIT 10;
-- At 100K+ active drivers this query takes 500ms+ per request
```

### Correct: Use in-memory geospatial index with H3 partitioning

```python
# GOOD — O(1) cell lookup + O(k) for k neighbors, typically <5ms
pickup_cell = h3.latlng_to_cell(40.7128, -74.006, 9)
search_cells = h3.grid_disk(pickup_cell, 1)  # ~7 cells
candidates = []
for cell in search_cells:
    candidates.extend(driver_index.get(cell, []))
# Only scores ~10-50 candidates instead of 100K+ drivers
```

### Wrong: Synchronous payment processing blocking trip completion

```python
# BAD — rider waits for payment processing before seeing "trip complete"
def complete_trip(trip_id):
    trip = db.get_trip(trip_id)
    trip.status = 'completed'
    charge_result = stripe.charges.create(amount=trip.fare)  # 2-5 second blocking call
    if charge_result.status != 'succeeded':
        trip.status = 'payment_failed'  # rider stuck in limbo
    db.save(trip)
```

### Correct: Async payment via event queue with retry

```python
# GOOD — trip completes instantly, payment processed asynchronously
def complete_trip(trip_id):
    trip = db.get_trip(trip_id)
    trip.status = 'completed'
    db.save(trip)
    kafka.produce('trip.completed', {
        'trip_id': trip_id,
        'fare': trip.fare,
        'idempotency_key': f"trip-{trip_id}"
    })
    # Payment Service consumes event, retries on failure, emits PaymentCompleted
```

### Wrong: Single-point surge pricing for an entire city

```python
# BAD — one surge multiplier for the whole city
total_requests = get_city_wide_requests()
total_drivers = get_city_wide_drivers()
city_surge = total_requests / total_drivers  # meaningless average
```

### Correct: Per-cell surge pricing using H3 hexagonal grid

```python
# GOOD — granular surge per H3 cell captures local supply/demand imbalance
for cell_id in active_cells:
    requests = get_cell_requests(cell_id, window_minutes=5)
    drivers = get_cell_drivers(cell_id)
    surge = calculate_surge(cell_id, requests, drivers)
    cache.set(f"surge:{cell_id}", surge, ttl=60)
```

## Common Pitfalls

- **Stale driver locations**: Drivers that close the app without going offline remain in the index as "available." Fix: Set a TTL on driver location entries (30s) and require periodic heartbeats; evict stale entries automatically. [src3]
- **GPS drift in tunnels/garages**: GPS signal loss causes drivers to "teleport" when signal returns, triggering false surge spikes. Fix: Apply a Kalman filter or speed-based sanity check — discard jumps > 200km/h. [src1]
- **Hot-spot matching storms**: A concert or sports event ending creates 10K requests in one H3 cell. Fix: Expand the search radius dynamically (increase k-ring) and implement a request queue with ETA-based prioritization. [src4]
- **Payment authorization expiry**: Pre-authorized holds typically expire after 7 days. If a disputed trip is not resolved within that window, you cannot capture the original authorization. Fix: Capture within 24 hours; for disputes, create a new charge with documented reason. [src5]
- **Cascading driver rejection**: If the top-matched driver declines, naively re-running the full matching algorithm wastes time. Fix: Pre-rank the top 5 candidates and cascade through the list with 10-15s timeouts per driver. [src2]
- **Inconsistent trip state across services**: Rider app shows "matched" but driver app shows "cancelled" due to eventual consistency. Fix: Use a single source of truth (Trip Service with event sourcing) and derive all views from the event log. [src5]
- **ETA inaccuracy from Euclidean distance**: Calculating ETA as straight-line distance / average speed ignores road networks. Fix: Use a road-graph routing engine (OSRM, Valhalla, Google Directions API) for all user-facing ETAs. [src4]
- **WebSocket connection leaks**: Failed connections that are not cleaned up exhaust server file descriptors. Fix: Implement connection health checks (ping/pong every 30s) and automatic cleanup of dead connections. [src5]

## Diagnostic Commands

```bash
# Check Redis GEO driver count in radius
redis-cli GEOSEARCH drivers:geo FROMLONLAT -74.006 40.7128 BYRADIUS 3 km COUNT 100 ASC

# Monitor Kafka consumer lag for location topic
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group location-consumer

# Count active WebSocket connections
ss -s | grep -i estab  # or: ws_server_metrics | grep active_connections

# Check H3 cell for a coordinate
python3 -c "import h3; print(h3.latlng_to_cell(40.7128, -74.006, 9))"

# Monitor trip state transitions (Kafka)
kafka-console-consumer.sh --bootstrap-server kafka:9092 --topic trip.events --from-latest

# Check surge pricing for a cell
redis-cli GET surge:891f1a80537ffff
```

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Building a two-sided marketplace connecting riders with drivers in real-time | Building a food delivery platform with restaurant prep time | Food delivery system design (different matching + batching) |
| Need to handle >1K concurrent ride requests with <3s matching latency | Simple point-to-point scheduled shuttle service | Queue-based booking system |
| Implementing dynamic pricing based on real-time supply/demand | Fixed-route public transit scheduling | Transit scheduling system (GTFS-based) |
| Require real-time driver tracking and ETA updates for riders | Package/freight logistics with multi-day delivery windows | Logistics/fleet management system design |
| Supporting multiple vehicle types (economy, premium, XL) in one platform | Peer-to-peer car rental (no real-time matching needed) | Marketplace platform design (Airbnb-style) |

## Important Caveats

- The H3 resolution level matters significantly: resolution 9 (~174m edge) is suitable for urban areas, but rural matching may need resolution 7 (~5.2km edge) to find enough drivers in a cell's k-ring
- Surge pricing algorithms are under active regulatory scrutiny — California AB5, EU Digital Services Act, and NYC TLC all impose constraints on algorithmic pricing; always build in jurisdiction-specific caps and transparency mechanisms
- Driver matching at Uber-scale (>1M concurrent) uses ML ranking models trained on acceptance/completion data — the weighted scoring shown here is a solid starting point but production systems evolve toward learned models
- WebSocket at scale is expensive — each persistent connection consumes server resources; at >100K concurrent drivers, consider a connection broker (e.g., RingPop, NATS) rather than a monolithic WebSocket server
- Payment processing varies dramatically by country — different payment methods (M-Pesa in Kenya, PIX in Brazil, UPI in India), different regulations, different settlement timelines; design the payment service with pluggable provider adapters from day one

## Related Units

- [Monolith to Microservices Migration](/software/migrations/monolith-to-microservices/2026)
