Skip to main content

Design a Ride-Sharing/Booking system

Problem Statement

Design a ride-sharing system where users can request rides from one location to another. The system should match users with nearby drivers, allow drivers to accept rides, and complete trips while tracking ride status.


Functional Requirements

  • Users should be able to request a ride from source to destination
  • System should match the ride with an available nearby driver
  • Driver should be able to accept or reject a ride request
  • Ride status should be tracked (REQUESTED, ACCEPTED, COMPLETED, CANCELLED)
  • Once a ride is completed, driver becomes available again

Objects Required

  • User
  • Driver
  • Location
  • Ride
  • RideStatus
  • RideService
  • DriverMatchingStrategy

RideStatus Enum


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

This enum represents the lifecycle of a ride. Every ride starts in a requested state and moves through different stages depending on driver and system actions.


Location Class


public class Location {

    double x;
    double y;

    public Location(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double distanceFrom(Location other) {
        return Math.sqrt(
            Math.pow(this.x - other.x, 2) +
            Math.pow(this.y - other.y, 2)
        );
    }
}

The constructor simply stores the coordinates of a point in the city.

The distanceFrom() method calculates the distance between two locations. This helps in finding the nearest driver during ride matching.


User Class


public class User {

    private String userId;
    private String name;
    private Location location;

    public User(String userId, String name, Location location) {
        this.userId = userId;
        this.name = name;
        this.location = location;
    }

    public Location getLocation() {
        return location;
    }

    public String getName() {
        return name;
    }
}

The constructor initializes a user with identity details and current location.

The getLocation() method is used by the system to determine where the user is requesting the ride from.

The getName() method is used for display and logging purposes.


Driver Class


public class Driver {

    private String driverId;
    private String name;
    private Location location;
    private boolean available;

    public Driver(String driverId, String name, Location location) {
        this.driverId = driverId;
        this.name = name;
        this.location = location;
        this.available = true;
    }

    public boolean isAvailable() {
        return available;
    }

    public void setAvailable(boolean available) {
        this.available = available;
    }

    public Location getLocation() {
        return location;
    }
}

The constructor initializes a driver and marks them available by default.

The isAvailable() method helps the system decide whether this driver can be assigned a ride.

The setAvailable() method updates driver status when a ride is accepted or completed.

The getLocation() method is used for matching the closest driver to a user.


Ride Class


public class Ride {

    private String rideId;
    private User user;
    private Driver driver;
    private Location source;
    private Location destination;
    private RideStatus status;

    public Ride(String rideId, User user, Location source, Location destination) {
        this.rideId = rideId;
        this.user = user;
        this.source = source;
        this.destination = destination;
        this.status = RideStatus.REQUESTED;
    }

    public void assignDriver(Driver driver) {
        this.driver = driver;
        this.status = RideStatus.ACCEPTED;
        driver.setAvailable(false);
    }

    public void completeRide() {
        this.status = RideStatus.COMPLETED;
        driver.setAvailable(true);
    }

    public RideStatus getStatus() {
        return status;
    }
}

The constructor creates a ride request and sets its initial status as REQUESTED.

The assignDriver() method connects a driver to the ride and marks the ride as accepted. It also immediately marks the driver as unavailable so they don’t get assigned another ride.

The completeRide() method marks the ride as finished and frees the driver so they can accept new requests again.

The getStatus() method helps external systems track the current state of the ride.


DriverMatchingStrategy Interface


import java.util.*;

public interface DriverMatchingStrategy {

    Driver findDriver(List drivers, Location source);
}

This interface defines the contract for matching drivers. Different strategies like nearest-driver-first or rating-based matching can be implemented later without changing core logic.


NearestDriverMatchingStrategy


import java.util.*;

public class NearestDriverMatchingStrategy implements DriverMatchingStrategy {

    public Driver findDriver(List drivers, Location source) {

        Driver bestDriver = null;
        double minDistance = Double.MAX_VALUE;

        for (Driver driver : drivers) {

            if (!driver.isAvailable()) continue;

            double dist = driver.getLocation().distanceFrom(source);

            if (dist < minDistance) {
                minDistance = dist;
                bestDriver = driver;
            }
        }

        return bestDriver;
    }
}

The method loops through all drivers and ignores those who are not available.

It calculates distance from the user’s location and picks the closest driver.

This keeps matching logic flexible and replaceable in the future.


RideService Class


import java.util.*;

public class RideService {

    private List drivers;
    private DriverMatchingStrategy strategy;

    public RideService(List drivers,
                       DriverMatchingStrategy strategy) {
        this.drivers = drivers;
        this.strategy = strategy;
    }

    public Ride requestRide(User user,
                            Location source,
                            Location destination) {

        Ride ride = new Ride(
                UUID.randomUUID().toString(),
                user,
                source,
                destination
        );

        Driver driver = strategy.findDriver(drivers, source);

        if (driver == null) {
            System.out.println("No driver available");
            return null;
        }

        ride.assignDriver(driver);
        return ride;
    }

    public void completeRide(Ride ride) {
        ride.completeRide();
    }
}

The constructor initializes the system with a pool of drivers and a matching strategy.

The requestRide() method creates a new ride request and asks the strategy to find the best available driver. If no driver is found, the request fails gracefully.

If a driver is found, the ride is assigned immediately and marked as active.

The completeRide() method simply delegates completion logic to the Ride class.


Main Class


import java.util.*;

public class Main {

    public static void main(String[] args) {

        User user =
            new User("U1", "Prasanna", new Location(10, 10));

        Driver d1 =
            new Driver("D1", "Driver A", new Location(11, 10));

        Driver d2 =
            new Driver("D2", "Driver B", new Location(20, 20));

        List drivers =
            Arrays.asList(d1, d2);

        RideService service =
            new RideService(
                drivers,
                new NearestDriverMatchingStrategy()
            );

        Ride ride =
            service.requestRide(
                user,
                new Location(10, 10),
                new Location(15, 15)
            );

        if (ride != null) {
            System.out.println("Ride started successfully");
        }

        service.completeRide(ride);

        System.out.println("Ride completed");
    }
}

The main method creates users and drivers, then initializes the ride service.

A ride request is made, and the system automatically assigns the nearest available driver.

Finally, the ride is completed and the driver becomes available again.


Class Diagram

UseruserId : Stringname : Stringlocation : LocationgetLocation() : LocationgetName() : StringDriverdriverId : Stringname : Stringlocation : Locationavailable : booleanisAvailable() : booleansetAvailable(available : boolean) : voidgetLocation() : LocationLocationx : doubley : doubledistanceFrom(other : Location) : doubleRiderideId : Stringuser : Userdriver : Driversource : Locationdestination : Locationstatus : RideStatusassignDriver(driver : Driver) : voidcompleteRide() : voidgetStatus() : RideStatusRideServicedrivers : List<Driver> source : Location,requestRide(user : User,destination : Location) : Ride completeRide(ride : Ride) : voidDriverMatchingStrategyfindDriver(drivers : List<Driver>, source : Location) : DriverNearestDriverMatchingStrategyfindDriver(drivers : List<Driver>, source : Location) : DriverRideStatusREQUESTEDACCEPTEDCOMPLETEDCANCELLED

Also See

Comments

Popular posts from this blog

Designing a Parking Lot - Low Level Design

Problem Statement Design a parking lot that can handle vehicles entering and leaving while managing parking across multiple floors. Each vehicle should be assigned a suitable parking spot based on its type, and the spot should be freed once the vehicle exits. The design should also support generating a ticket at entry and optionally calculating the parking fee based on the duration of stay. Asked In Companies Amazon Google Microsoft Uber Walmart Flipkart Meta PayPal Oracle Salesforce Adobe Apple Intuit LinkedIn Atlassian Functional Requirements The design should support multiple vehicle types such as bikes, cars, and trucks A vehicle must be assigned a parking spot compatible with its type A parking spot cannot be assigned to more than one vehicle at a time The parking lot should support multiple levels (floors) The design should search and allocate an availa...

Most Frequently Asked Low Level Design(LLD) Interview Questions

Below are the curated list of most commonly asked Low Level Design (LLD) interview problems. Each problem includes a short description and a link to the complete solution with code and class diagrams. Design Parking Lot System The system should handle parking for different vehicle types such as bikes, cars, and trucks. It should manage slot allocation, availability tracking, and entry/exit flow. The design also ensures efficient usage of parking space under varying load conditions. View Solution Design Elevator / Lift System The system should support multiple elevators operating across floors with request handling logic. It focuses on scheduling algorithms to minimize wait time and optimize movement. It also manages direction control and concurrent floor requests. View Solution Design Movie Ticket Booking System The system should allow users to browse movies, select shows, and book seats. It handles seat ...

Software Design Patterns for LLD Interviews: A Complete Guide

Software Design Patterns for LLD Interviews: A Complete Guide In Software Development Engineer (SDE) interviews—especially for mid-level and senior roles—low-level design (LLD) rounds assess your ability to write clean, reusable, maintainable, and extensible code. The foundation of resolving these architectural challenges lies in the standard Gang of Four (GoF) Design Patterns. Rather than memorizing theoretical definitions, interviewers expect you to apply these patterns to real-world scenarios, identifying the trade-offs of each. Below is a comprehensive guide to the 12 most frequently asked design patterns in LLD interviews, categorized by their classification (Creational, Structural, and Behavioral). Each pattern contains a concrete, real-world Java implementation and a detailed breakdown of design decisions. Creational Design Patterns Creational design patterns deal with object creation mechanisms. They abstract the instantiation process, making a system independent of how...