Design Uber

02 Aug 2026, Updated: 10 Aug 2026 10 min read
1
An Uber is a real-time ride-hailing platform that connects riders with nearby drivers.

The system should allow riders to request rides, locate nearby available drivers, match riders with suitable drivers, track driver locations in real time, calculate fares, process payments, and deliver trip notifications.

The system must support millions of concurrent users while maintaining low latency, high availability, horizontal scalability, and fault tolerance.

Requirements

Functional Requirements

The Uber system should satisfy the following functional requirements.

1. Allow riders to request rides.
2. Find nearby available drivers.
3. Match riders with suitable drivers.
4. Track driver locations in real time.
5. Maintain the ride lifecycle from request to completion.
6. Calculate trip fares.
7. Process payments.
8. Send ride and payment notifications.
9. Support ride history and status retrieval.

Non-Functional Requirements

The system should also satisfy the following non-functional requirements.

1. Very low latency for ride matching.
2. High availability.
3. Horizontal scalability.
4. Real-time location processing.
5. Fault tolerance.
6. Reliable ride and payment processing.
7. Protection against duplicate ride assignments and fraudulent requests.

API Design

The Uber platform can expose REST APIs for ride requests, driver location updates, and ride status retrieval.

Request Ride

Request:
POST /api/v1/rides

{
  "pickup": {
    "latitude": 28.6139,
    "longitude": 77.2090
  },
  "destination": {
    "latitude": 28.5355,
    "longitude": 77.3910
  }
}
Response:
{
  "rideId": "R12345",
  "status": "SEARCHING_DRIVER"
}

Update Driver Location

Request:
POST /api/v1/drivers/location

{
  "driverId": "D101",
  "latitude": 28.6145,
  "longitude": 77.2102
}
Response:
{
  "status": "UPDATED"
}

Retrieve Ride

Request:
GET /api/v1/rides/R12345
Response:
{
  "rideId": "R12345",
  "riderId": "U101",
  "driverId": "D101",
  "status": "DRIVER_ARRIVING",
  "fare": 420.50
}

High-Level Architecture

The following diagram illustrates the high-level architecture of an Uber-like ride-hailing platform.

Each service can be scaled independently according to its workload.

API Gateway

The API Gateway acts as the single entry point for rider and driver applications.

It authenticates requests, validates input, applies rate limiting, routes requests to the appropriate backend service, and hides the internal service topology from clients.

For example, a ride request is routed to the Ride Service, while a driver GPS update is routed to the Location Service.

The API Gateway can also enforce authorization rules so that a rider cannot modify another user's ride and a driver cannot update the location of another driver.

Ride Service

The Ride Service is the core component responsible for managing the complete ride lifecycle.

The RIDE table stores ride details, including the rider and driver assignment, pickup and destination locations, fare, ride status, and important timestamps.
RIDE
------------------------------------------------------------------------------------------------------------
ride_id | rider_id | driver_id | pickup              | destination        | fare   | status            | created_at
------------------------------------------------------------------------------------------------------------
R12345  | U101     | D201      | Connaught Place     | Noida Sector 18     | 420.50 | DRIVER_ASSIGNED   | 10:00
R12346  | U205     | D305      | Saket               | Gurgaon Cyber City | 650.00 | ON_TRIP           | 10:05
R12347  | U310     | NULL      | Karol Bagh          | Dwarka              | 380.00 | SEARCHING_DRIVER  | 10:10
R12348  | U412     | D462      | Hauz Khas           | Airport             | 520.00 | COMPLETED         | 09:30
When a rider requests a ride, the Ride Service validates the pickup and destination locations, creates the ride with the initial status SEARCHING_DRIVER, and asks the Location Service to find nearby drivers.

The Location Service returns nearby DriverIds ordered by distance. The Ride Service then retrieves driver information from the Driver Service and filters the candidates based on availability and dispatch rules.

If a driver is currently ON_TRIP, the Ride Service can estimate when the driver will become available and calculate the expected pickup time. This allows the system to support assigning a future ride to a driver who is currently completing another ride.

The Ride Service then sends ride requests to suitable drivers.

When a driver accepts a ride, the Ride Service must ensure that the same driver cannot be assigned to multiple rides concurrently. The driver assignment therefore requires an atomic state transition.

For example, a driver can transition from AVAILABLE to ON_TRIP, or from ON_TRIP to HAVE_NEXT_TRIP when accepting a future ride. A simplified transaction can look like:
BEGIN;

UPDATE DRIVER
SET status =
    CASE
        WHEN status = 'AVAILABLE' THEN 'ON_TRIP'
        WHEN status = 'ON_TRIP' THEN 'HAVE_NEXT_TRIP'
    END
WHERE driver_id = 'D101'
  AND status IN ('AVAILABLE', 'ON_TRIP');

-- Continue only if exactly one row was updated.

INSERT INTO RIDE
(
    ride_id,
    rider_id,
    driver_id,
    pickup,
    destination,
    fare,
    status
)
VALUES
(
    'R12345',
    'U101',
    'D101',
    'Connaught Place',
    'Noida Sector 18',
    420.50,
    'DRIVER_ASSIGNED'
);

COMMIT;
The conditional update is important because multiple ride requests may attempt to assign the same driver simultaneously. The database guarantees that only one request can successfully transition the driver's state.

For an AVAILABLE driver, the state changes to ON_TRIP. For a driver already ON_TRIP, the state changes to HAVE_NEXT_TRIP, preventing another ride from being assigned as the next trip.

After the transaction commits successfully, the Ride Service publishes a RideAssigned event to Kafka. The Notification Service can consume this event and notify the rider and driver.

Driver Service

The Driver Service manages driver profiles, vehicle information, availability, and driver-related metadata.

A simplified driver table can look like:
DRIVER
--------------------------------------------------------------------------------
driver_id | name         | vehicle_number | status
--------------------------------------------------------------------------------
D201      | Amit Sharma  | DL01AB1234     | AVAILABLE
D305      | Rahul Verma  | DL05CD5678     | ON_TRIP
D412      | Priya Singh  | HR26EF9012     | OFFLINE
D462      | Ankur Singh  | UP21ET6042     | HAVE_NEXT_TRIP
The driver status determines whether the driver can participate in ride matching. Typical states include AVAILABLE, ON_TRIP, HAVE_NEXT_TRIP, and OFFLINE.

The Driver Service provides driver metadata and availability information to the Ride Service. The driver's real-time location is maintained separately by the Location Service.

Location Service

The Location Service processes the continuous stream of GPS updates generated by driver applications. A driver application periodically sends its latest coordinates.

The Location Service stores the driver's latest location in a Redis Geospatial Index. For example:
GEOADD drivers
77.2090 28.6139 D101
77.2145 28.6200 D205
77.2001 28.6105 D310
Redis stores the geographic coordinates together with the driver identifier. The system does not need to scan every driver to find nearby vehicles. Instead, Redis performs a geospatial search.
GEOSEARCH drivers
FROMLOC 77.2090 28.6139
BYRADIUS 5 km
ASC
COUNT 5
The query returns nearby drivers ordered by distance:
D101
D310
D205
The Location Service returns these candidate DriverIds to the Ride Service.

The Ride Service then retrieves driver details, checks availability, considers current trip status and expected pickup time, and applies the final dispatch rules before sending the ride request.

Ride Request Flow

When a rider requests a ride, the request reaches the API Gateway, which authenticates the rider and routes the request to the Ride Service. The Ride Service validates the request, creates the ride with the status SEARCHING_DRIVER, and asks the Location Service to find nearby drivers.

The Location Service queries the Redis geospatial index instead of scanning the driver database and returns nearby driver IDs ordered by distance. The Ride Service then retrieves driver details and filters out drivers who are offline, already assigned to another ride, or otherwise unavailable.

The platform maintains a persistent WebSocket connection with each online driver. The Ride Service can send requests sequentially or to multiple suitable drivers in parallel to reduce matching latency.

When a driver accepts the ride, the Ride Service performs an atomic assignment. If the driver is still available, the assignment succeeds. If another ride has already assigned the driver, the conditional update fails and the Ride Service selects another candidate.

After successful assignment, the ride changes to DRIVER_ASSIGNED. The Ride Service publishes a RideAssigned event to Kafka. The Notification Service consumes the event and notifies the rider. Any remaining pending driver requests are cancelled.

Ride Lifecycle

Every ride progresses through a well-defined lifecycle.

A ride starts in the REQUESTED state when the rider submits the request. The Ride Service then changes it to SEARCHING_DRIVER while nearby drivers are being located.

After a driver accepts the request and the assignment succeeds atomically, the ride becomes DRIVER_ASSIGNED. As the driver travels toward the pickup location, the status changes to DRIVER_ARRIVING.

Once the rider enters the vehicle and the trip starts, the status becomes IN_PROGRESS. After the driver reaches the destination, the ride moves to COMPLETED.

If no driver accepts within a configured timeout, the Ride Service can expand the search radius and retry the matching process. If no suitable driver is found after all retries, the ride is marked as CANCELLED and the rider is notified.

Fare & Payments

The fare depends on several factors such as the base fare, travel distance, trip duration, and surge pricing.

A simplified fare calculation can be represented as:
Fare =
    Base Fare
  + Distance Charge
  + Time Charge
  Γ— Surge Multiplier
The estimated fare can be calculated before the ride begins. The final fare can be calculated when the trip completes because the actual distance and duration may differ from the initial estimate.

After the trip completes, the Payment Service calculates or receives the final amount from the Ride Service and processes the payment through an external payment gateway.

After successful payment, the Payment Service records the transaction and publishes a payment event to Kafka.

The Notification Service can consume the event and send the payment confirmation and receipt to the rider.

High Availability

The Uber platform must continue operating even if individual application instances fail.

Multiple instances of every stateless service run behind a Load Balancer. If one instance fails, traffic is routed to another healthy instance.

Critical infrastructure such as Redis, Kafka, and the relational database should use replication and automatic failover.

For example, Redis can be deployed as a cluster so that the failure of an individual node does not make the entire location system unavailable.

Kafka replicates partitions across brokers so that events remain available if a broker fails.

The relational database can use primary-replica replication and automated failover for transactional availability.

For large-scale deployments, the system can also be deployed across multiple geographic regions.

Ride traffic can be routed to the closest healthy region to reduce latency and improve regional fault tolerance.

Scalability

The application layer scales horizontally by adding more instances of services such as the API Gateway, Ride Service, Driver Service, Location Service, Payment Service, and Notification Service.

Because these services are largely stateless, additional instances can be added without changing application logic.

The Location Service is particularly important because driver applications generate a continuous stream of GPS updates.

These updates can be distributed across multiple Location Service instances.

Redis can be deployed as a cluster to distribute geospatial data and workload across multiple nodes.

Kafka partitions allow ride events to be processed concurrently by multiple consumers.

The transactional database can be scaled using read replicas, partitioning, and sharding where necessary.

Ride data can eventually be partitioned by city or region because most ride operations are geographically localized.

Conclusion

An Uber-like platform appears simple to users, but supporting millions of concurrent riders and drivers requires real-time location processing, geospatial matching, distributed ride assignment, and high availability.

The critical design is to keep durable transactional data in a relational database, maintain rapidly changing driver locations in Redis Geospatial Indexes, use Kafka for asynchronous event processing, and scale stateless services horizontally.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer β€’ Java β€’ Python β€’ Distributed Systems β€’ AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

πŸ’¬ Comments

Join the Discussion