Problem 4.11 — Ride Booking (LLD)
In one line
Driver/rider matching, pricing (surge), trip lifecycle and the 'find nearest available driver' core.
Think of it like this
Booking an Ola or Uber. The app needs to instantly find the nearest free driver, and it must make absolutely sure two riders can't both get matched with the same driver at the same moment.
Key ideas
- 01
Entities: Rider, Driver, RideRequest, Trip, FareQuote, Location (lat, lng).
- 02
Matching: drivers publish availability; a request triggers a nearest-first scan (geo index or grid) within radius.
- 03
Matching states: REQUESTED → MATCHED (driver accept) → EN_ROUTE → ON_TRIP → COMPLETED; expiry of unaccepted requests.
- 04
One-driver-one-ride invariant: a driver can accept only one request — atomic claim (same pattern as seats).
- 05
Fare: base + time × rate + distance × rate (+ surge multiplier when demand/balance crosses threshold) — policy strategy.
- 06
Live location stream = driver position heartbeat → trip tracking.
- 07
Extensibility: new vehicle tier, new promo rule, surge algorithm — seams again.
Java / Spring map
- →
DriverAvailability as a concurrent registry keyed by driverId; matching takes a radius+direction filter.
Code & diagrams
Nearest-first scan plus the atomic 'claim this driver' step, guarded exactly like a parking spot.
public final class Driver {
private final String id;
private volatile Location location;
private final AtomicBoolean available = new AtomicBoolean(true);
public Driver(String id, Location loc) { this.id = id; this.location = loc; }
public boolean tryClaim() { return available.compareAndSet(true, false); } // atomic: only one rider wins
public void release() { available.set(true); }
public Location location() { return location; }
public String id() { return id; }
}
public final class DriverMatcher {
private final Map<String, Driver> byId = new ConcurrentHashMap<>();
private final GeoIndex geoIndex; // grid or geohash bucket lookup, O(nearby) not O(all)
public DriverMatcher(GeoIndex geoIndex) { this.geoIndex = geoIndex; }
public Optional<Driver> match(Location pickup, double radiusKm) {
return geoIndex.nearby(pickup, radiusKm).stream()
.map(byId::get)
.filter(Objects::nonNull)
.sorted(Comparator.comparingDouble(d -> pickup.distanceKm(d.location())))
.filter(Driver::tryClaim) // first driver whose CAS succeeds wins the race
.findFirst();
// Two riders calling match() at the same instant will iterate the same sorted list, but
// compareAndSet ensures only one of them gets true from tryClaim() on any given driver.
}
}Explain without notes
Hold on: how do you stop two riders matching the same driver in the same 100ms? Name the mechanism.
Practice
Implement the one-driver atomic accept with a ConcurrentHashMap claim or DB versioned row.
Trade-offs
- ↔
Nearest-first is local-optimal; global optimization (ETA-min) is expensive at scale — state which NFR drives the choice.
Completion checklist
I can model request→match→trip lifecycle and defend the atomic driver claim.