Back to LLD explorer
Proximity / Dispatch• Patterns: Observer, Strategy, State

Uber

Hard

Problem Summary

Design a high-scale Ride-Hailing Dispatch engine matching riders with nearby drivers using geospatial proximity indexing, dynamic surge multipliers, and trip state management.

Functional Scope

  • Real-time GPS location ingestion from active driver fleet.
  • Proximity matching algorithm querying nearest drivers within radius R.
  • Pluggable surge pricing strategy based on local demand-supply ratio.
  • Trip lifecycle state machine: Requested, Accepted, Arrived, InTrip, Completed.
  • Observer pattern for live GPS tracking and ETA recalculation.

Entity-Relationship (ER) Schema

Trip [1] --> [1] Rider
Trip [1] --> [0..1] Driver
Trip [1] --> [1] TripState
DispatchEngine [1] --> [1] MatchingStrategy
DispatchEngine [1] --> [1] PricingStrategy

Design Approach

Apply the Strategy pattern to isolate candidate matching and surge pricing algorithms. Model trip lifecycles via the State pattern to prevent invalid transitions.

Core Classes & Models

DispatchEngine (Orchestrates geospatial queries and driver ride offers)Trip (TripId, rider, driver, pickup, dropoff, fare, TripState)Location (Latitude, longitude)MatchingStrategy (NearestProximityStrategy, BatchBipartiteMatchingStrategy)PricingStrategy (SurgePricingStrategy, FlatDistancePricing)TripObserver (Pushes live vehicle movement to rider app)
Code Blueprint
74 lines
/**
 * Low-Level Design: Uber Ride-Hailing Service
 * Design Patterns Applied:
 *  - Observer Pattern: Real-time driver matching and ride status updates to Rider and Driver
 *  - Strategy Pattern: Dynamic surge pricing and nearest-driver dispatch algorithms
 *  - State Pattern: Ride lifecycle states (Requested, Accepted, InTransit, Completed)
 */

import java.util.*;

enum RideStatus { REQUESTED, ACCEPTED, IN_TRANSIT, COMPLETED, CANCELLED }

// --- [DESIGN PATTERN: Strategy] Surge and fare estimation strategy ---
interface PricingStrategy {
    double calculateFare(double distanceKm, double surgeMultiplier);
}

class StandardPricingStrategy implements PricingStrategy {
    @Override
    public double calculateFare(double distanceKm, double surgeMultiplier) {
        return (5.0 + distanceKm * 2.0) * surgeMultiplier;
    }
}

// --- [DESIGN PATTERN: Observer] Ride notification observer ---
interface RideObserver {
    void onRideStatusChanged(String rideId, RideStatus status, String message);
}

class RiderClient implements RideObserver {
    private final String riderId;
    public RiderClient(String riderId) { this.riderId = riderId; }
    @Override
    public void onRideStatusChanged(String rideId, RideStatus status, String message) {
        System.out.printf("🚗 [Rider App %s] Ride %s is now %s (%s)%n", riderId, rideId, status, message);
    }
}

class Ride {
    private final String id;
    private final String riderId;
    private String driverId;
    private RideStatus status = RideStatus.REQUESTED;
    private final List<RideObserver> observers = new ArrayList<>();

    public Ride(String id, String riderId) { this.id = id; this.riderId = riderId; }
    public String getId() { return id; }
    public void addObserver(RideObserver o) { observers.add(o); }

    public void updateStatus(RideStatus newStatus, String msg) {
        this.status = newStatus;
        for (RideObserver o : observers) o.onRideStatusChanged(id, newStatus, msg);
    }

    public void assignDriver(String driverId) {
        this.driverId = driverId;
        updateStatus(RideStatus.ACCEPTED, "Driver " + driverId + " accepted request");
    }
}

public class Main {
    public static void main(String[] args) {
        PricingStrategy pricing = new StandardPricingStrategy();
        double fare = pricing.calculateFare(12.5, 1.5);
        System.out.printf("Estimated Surge Fare: $%.2f%n", fare);

        Ride ride = new Ride("RIDE-902", "RIDER-101");
        ride.addObserver(new RiderClient("RIDER-101"));

        ride.assignDriver("DRIVER-404");
        ride.updateStatus(RideStatus.IN_TRANSIT, "Heading to destination");
        ride.updateStatus(RideStatus.COMPLETED, "Arrived at destination");
    }
}