Design an in-memory ride-hailing system that allows riders to request trips, matches them with nearby drivers, tracks trip progress, calculates fares, supports cancellations, and processes payments.
The public methods model the main booking, tracking, cancellation, pricing, and payment operations of a ride-hailing API.
RideHailingSystem
(locationX, locationY).REQUESTED, ACCEPTED, IN_PROGRESS, COMPLETED, and CANCELLED.REQUESTED → ACCEPTED → IN_PROGRESS → COMPLETED.REQUESTED, ACCEPTED, or IN_PROGRESS may instead move to CANCELLED.COMPLETED and CANCELLED are terminal trip states.public RideHailingSystem(long baseFareInCents, long farePerDistanceUnitInCents, int maxPaymentAttempts, List<String> drivers)
drivers uses the comma-separated format "driverId,driverName,locationX,locationY".baseFareInCents is charged for every completed trip.farePerDistanceUnitInCents is charged for every actual distance unit travelled.maxPaymentAttempts is the maximum number of distinct payment attempts allowed for one trip.public boolean addRider(String riderId, String riderName)
true when the rider is added.false if riderId already exists.public boolean updateDriverLocation(String driverId, int locationX, int locationY)
false if the driver does not exist.true.public boolean setDriverAvailability(String driverId, boolean available)
false if the driver does not exist or is assigned to a nonterminal trip.true.public int updateSurgePricing(String zoneId, int activeRequests, int availableDrivers)
zoneId using the supplied market-demand values.activeRequests is 0, the multiplier is 100.activeRequests is positive and availableDrivers is 0, the multiplier is 300.activeRequests ≤ availableDrivers, the multiplier is 100.availableDrivers < activeRequests ≤ 2 × availableDrivers, the multiplier is 150.200.100.public long estimateFare(String zoneId, int estimatedDistanceUnits)
subtotal = baseFareInCents + estimatedDistanceUnits × farePerDistanceUnitInCents.ceil(subtotal × surgeMultiplierPercentage / 100).public String requestTrip(String tripId, String riderId, int pickupX, int pickupY, int destinationX, int destinationY, String zoneId, String paymentMethodId)
(driverX - pickupX) × (driverX - pickupX) + (driverY - pickupY) × (driverY - pickupY).driverId is selected.REQUESTED.driverId when successful.public boolean acceptTrip(String tripId, String driverId)
REQUESTED to ACCEPTED.true only if the trip is currently REQUESTED and driverId is its assigned driver.false.public boolean startTrip(String tripId, String driverId)
ACCEPTED to IN_PROGRESS.true only if driverId is the assigned driver and the trip is currently ACCEPTED.false.public long completeTrip(String tripId, String driverId, int actualDistanceUnits)
IN_PROGRESS and driverId is its assigned driver.subtotal = baseFareInCents + actualDistanceUnits × farePerDistanceUnitInCents.ceil(subtotal × storedSurgeMultiplierPercentage / 100).COMPLETED, its payment status becomes PENDING, the driver's location becomes the trip's destination, and the driver becomes available again.-1 when the trip cannot be completed.public boolean cancelTrip(String tripId, String requesterId)
REQUESTED, ACCEPTED, or IN_PROGRESS.requesterId must be either the trip's rider or its assigned driver.0 and does not create a payment.true when the trip is cancelled.false if any cancellation rule is not satisfied.public String processPayment(String tripId, String paymentRequestId, boolean gatewaySuccessful)
COMPLETED trip.paymentRequestId represents one payment attempt.paymentRequestId is submitted again for the same trip, its original result is returned without creating another attempt. The new value of gatewaySuccessful is ignored.SUCCEEDED and returns "SUCCEEDED".RETRY_PENDING and the method returns "RETRY_PENDING".FAILED and the method returns "FAILED".SUCCEEDED or FAILED, a new payment request returns that status without creating another attempt."INVALID_REQUEST" when payment processing is not allowed.public String getTripDetails(String tripId)
"tripId,riderId,driverId,tripStatus,pickupX,pickupY,destinationX,destinationY,zoneId,surgeMultiplierPercentage,fareInCents,paymentStatus,paymentAttempts".fareInCents is 0.paymentStatus is NOT_STARTED.1 ≤ baseFareInCents ≤ 1,000,0000 ≤ farePerDistanceUnitInCents ≤ 1,000,0001 ≤ maxPaymentAttempts ≤ 100 ≤ drivers.size() ≤ 100,000driverId and other id values are globally unique.1 ≤ riderId.length(), driverId.length(), tripId.length() ≤ 1001 ≤ riderName.length(), driverName.length() ≤ 1001 ≤ zoneId.length(), paymentMethodId.length(), paymentRequestId.length() ≤ 100-1,000,000 ≤ locationX, locationY, pickupX, pickupY, destinationX, destinationY ≤ 1,000,0000 ≤ activeRequests, availableDrivers ≤ 1,000,0000 ≤ estimatedDistanceUnits, actualDistanceUnits ≤ 1,000,0001 ≤ total method calls ≤ 100,000new RideHailingSystem(baseFareInCents = 200, farePerDistanceUnitInCents = 50, maxPaymentAttempts = 3, drivers = ["driver-9,aman,2,1", "driver-3,ravi,0,3"])
addRider(riderId = "rider-2", riderName = "maya")
Output: true
updateSurgePricing(zoneId = "north", activeRequests = 4, availableDrivers = 2)
Output: 150
estimateFare(zoneId = "north", estimatedDistanceUnits = 8)
Output: 900
requestTrip(tripId = "trip-7", riderId = "rider-2", pickupX = 0, pickupY = 0, destinationX = 8, destinationY = 0, zoneId = "north", paymentMethodId = "card-2")
Output: "driver-9"
Driver driver-9 has squared distance 5 from the pickup, while driver-3 has squared distance 9.
getTripDetails(tripId = "trip-7")
Output: "trip-7,rider-2,driver-9,REQUESTED,0,0,8,0,north,150,0,NOT_STARTED,0"
acceptTrip(tripId = "trip-7", driverId = "driver-9")
Output: true
startTrip(tripId = "trip-7", driverId = "driver-9")
Output: true
completeTrip(tripId = "trip-7", driverId = "driver-9", actualDistanceUnits = 8)
Output: 900
The subtotal is 200 + 8 × 50 = 600. Applying the stored 150% multiplier produces a final fare of 900.
processPayment(tripId = "trip-7", paymentRequestId = "payment-attempt-1", gatewaySuccessful = false)
Output: "RETRY_PENDING"
processPayment(tripId = "trip-7", paymentRequestId = "payment-attempt-1", gatewaySuccessful = true)
Output: "RETRY_PENDING"
The repeated request returns its cached result and does not create another payment attempt.
processPayment(tripId = "trip-7", paymentRequestId = "payment-attempt-2", gatewaySuccessful = true)
Output: "SUCCEEDED"
getTripDetails(tripId = "trip-7")
Output: "trip-7,rider-2,driver-9,COMPLETED,0,0,8,0,north,150,900,SUCCEEDED,2"
new RideHailingSystem(baseFareInCents = 100, farePerDistanceUnitInCents = 20, maxPaymentAttempts = 2, drivers = ["driver-z,kabir,1,0", "driver-a,sara,-1,0"])
addRider(riderId = "rider-8", riderName = "neha")
Output: true
requestTrip(tripId = "trip-9", riderId = "rider-8", pickupX = 0, pickupY = 0, destinationX = 5, destinationY = 5, zoneId = "central", paymentMethodId = "card-8")
Output: "driver-a"
Both drivers have the same squared distance from the pickup, so the lexicographically smaller identifier is selected. The unchanged zone uses the default multiplier of 100.
cancelTrip(tripId = "trip-9", requesterId = "another-rider")
Output: false
cancelTrip(tripId = "trip-9", requesterId = "driver-a")
Output: true
getTripDetails(tripId = "trip-9")
Output: "trip-9,rider-8,driver-a,CANCELLED,0,0,5,5,central,100,0,NOT_STARTED,0"