Ride Booking System
Design ride booking like Uber — riders request trips, drivers accept, real-time tracking, fare calculation, and payment settlement.
Introduction
Design ride booking like Uber — riders request trips, drivers accept, real-time tracking, fare calculation, and payment settlement. Combines geo matching, surge pricing, trip state machine, and dual-sided mobile apps.
Location updates every few seconds from millions of drivers stress ingestion pipeline. Matching latency target under 30 seconds in dense cities.
Understanding the topic
Key concepts
- Trip states: REQUESTED → MATCHED → IN_PROGRESS → COMPLETED / CANCELLED.
- Supply-demand: drivers online in geo cell; riders request pickup/dropoff.
- Matching: broadcast offer to nearest N drivers; first accept wins.
- Surge: multiplier from demand/supply ratio per geo hex.
- Fare: base + distance + time + surge; calculated at end with GPS trail.
- Payment: authorize estimate, capture final, driver payout async.
flowchart TBRider --> MatchSvcMatchSvc --> GeoDriver --> LocationStreamMatchSvc --> TripSvc
Internal architecture
Architecture overview
flowchart TBRider --> MatchSvcMatchSvc --> GeoDriver --> LocationStreamMatchSvc --> TripSvc
Step-by-step explanation
- Rider POST /trips → Trip Service creates REQUESTED, publishes to Matching.
- Matching queries Redis GEO drivers within radius expanding until accept.
- Driver POST /trips/{id}/accept with distributed lock on tripId.
- Location stream: driver app → Kafka → stream processor updates Redis + rider map WS.
- Trip complete → Pricing engine → Payment capture → Driver wallet credit via payout queue.
- Surge service consumes demand/supply counters per H3 hex cell.
Informative example
Trip request and driver accept with optimistic locking:
@Servicepublic class TripService {private final TripRepository trips;private final MatchingClient matching;private final StringRedisTemplate redis;public TripResponse requestTrip(TripRequest req, String riderId) {Trip trip = trips.save(Trip.requested(riderId, req.pickup(), req.dropoff()));matching.findDriver(trip.id(), req.pickup());return TripResponse.from(trip);}public TripResponse accept(String tripId, String driverId) {String lockKey = "trip:lock:" + tripId;Boolean ok = redis.opsForValue().setIfAbsent(lockKey, driverId, Duration.ofSeconds(15));if (!Boolean.TRUE.equals(ok)) throw new TripAlreadyMatchedException();Trip trip = trips.findById(tripId).orElseThrow();trip.match(driverId);trips.save(trip);return TripResponse.from(trip);}}
H3 hex indexing for surge. Separate read-heavy map tile service. Outbox events for billing.
Real-world use
Real-world use cases
- Ride-hailing Uber/Lyft model.
- Fleet logistics B2B dispatch.
- Ambulance emergency prioritization queue.
- Autonomous vehicle fleet coordinator future variant.
Best practices
- Cell-based architecture per city for fault isolation.
- Idempotent trip request from rider double-tap.
- GPS trail stored compressed for fare dispute.
- Driver background check separate service boundary.
- Cancel policy fees in state machine transitions.
- Load test location ingestion separately from matching.
Common mistakes
- Global matching DB scan — O(n) drivers unacceptable.
- No lock on accept — two drivers same trip.
- Synchronous long polling blocks matching thread.
- Surge applied without cap — PR disaster.
- Payment capture before trip complete without adjustment logic.
Advanced interview questions
Q1BeginnerCore trip states?
Q2BeginnerHow match rider to driver?
Q3IntermediateLocation update pipeline?
Q4IntermediateSurge pricing design?
Q5AdvancedDesign Uber global 100M monthly trips.
Summary
Ride HLD combines geo matching, trip FSM, and location streaming. Redis GEO and cell architecture enable sub-second proximity queries. Distributed lock prevents double driver assignment. Surge pricing from real-time demand/supply analytics. Payment authorize/capture aligns with trip completion. Online payments deep-dive fraud and ledger patterns next.