Interview OOD Drill: Design Uber in 5 Classes (and Explain It Clearly)


Interview OOD Drill: Design Uber in 5 Classes (and Explain It Clearly)
If you can model a ride-hailing system like Uber with clean object-oriented design (OOD), you can handle many system-design interview problems. Here’s a compact, interview-friendly way to model the core domain in five classes, with responsibilities, state transitions, and common extensions.
High-level idea
Start small and defend the responsibilities you give each class. Focus on: core entities, coordinators that operate on those entities, and how the system evolves (state transitions). Keep the design open for pricing, cancellations, surge, driver ratings, etc.
The 5 classes (core model)
User
- Represents a person using the app (rider or driver account).
- Fields: id, name, contactInfo, paymentMethods, userType (RIDER / DRIVER) or role flag.
- Methods: updateProfile(), addPaymentMethod(), getLocation() (if available).
Driver (extends User)
- Inherits User. Adds domain-specific attributes and behavior.
- Fields: vehicleInfo, currentLocation, isAvailable, rating.
- Methods: updateLocation(), setAvailability(), acceptRide(), finishRide().
Ride
- Represents a single trip request and lifecycle.
- Fields: id, riderId, driverId (nullable until matched), pickupLocation, dropoffLocation, price, status.
- Status lifecycle: PENDING -> IN_PROGRESS -> COMPLETED (and other states: CANCELED, FAILED).
- Methods: transitionTo(newStatus) with validation, requestCancellation(), estimatePrice().
RideManager (coordinator)
- Responsible for matching riders to available drivers and managing ride state transitions.
- Responsibilities:
- Receive ride requests, find candidate drivers (by proximity, filters), and notify drivers.
- Assign accepted driver to Ride and move status from PENDING to IN_PROGRESS.
- Handle timeouts, retries, re-matching when drivers decline.
- Example API: requestRide(rider, pickup, dropoff) -> Ride; driverAccepts(rideId, driverId); cancelRide(rideId).
Payment (coordinator/service)
- Responsible for charging after ride completion and handling refunds/cancellations.
- Responsibilities:
- Calculate final fare (base fare + distance + time + surge + taxes + fees).
- Charge rider’s payment method and distribute payout to driver (or schedule payout).
- Handle failed payments and retries.
- Example API: charge(ride) -> PaymentReceipt; refund(ride).
State transitions (ride lifecycle)
PENDING: Rider requested. Searching for driver.
- on driver accept -> IN_PROGRESS
- on rider cancel -> CANCELED
- on timeout/no driver -> FAILED or RE-QUEUE
IN_PROGRESS: Driver accepted and trip started.
- on arrival at destination -> COMPLETED
- on user/driver cancel (rare after start) -> CANCELED
COMPLETED: Trip finished — trigger Payment. Mark driver available.
Make sure transition logic is centralized (e.g., Ride.transitionTo()) and validated to prevent invalid moves.
Example matching sequence (simplified)
- Rider calls RideManager.requestRide(rider, pickup, dropoff).
- RideManager creates Ride(status=PENDING) and queries available drivers nearby.
- RideManager notifies drivers (push) — first driver to accept calls driverAccepts(rideId, driverId).
- RideManager assigns driver: ride.driverId = driverId; ride.transitionTo(IN_PROGRESS).
- When driver reports trip end, RideManager calls ride.transitionTo(COMPLETED) and triggers Payment.charge(ride).
Responsibilities: how to defend this design in an interview
- Single Responsibility: Each class has a clear purpose — entities hold data and small behaviors, managers coordinate processes, payment encapsulates billing.
- Separation of concerns: RideManager handles matching and lifecycle, Payment handles money. This prevents mixing matching logic with billing logic.
- Extensibility: New features (surge pricing, cancellation policies, promos, shared rides) should be added as services or strategies rather than bloating Ride or RideManager.
- Testability: Keep side effects (network calls, DB, push notifications, payment gateway) out of pure logic; inject them as interfaces/clients so you can mock in tests.
Extensibility & common features
- Pricing strategies: Implement a PricingStrategy interface (FlatRate, DistanceBased, SurgePricing) and inject it into Payment or Ride for final fare calculation.
- Cancellations: Add cancellation policies with penalties. Implement as a CancellationPolicy service invoked by RideManager.
- Surge: Surge rules can be a separate service consulted by PricingStrategy.
- Ratings: Add a Rating service to allow drivers and riders to rate each other; store rating in Driver/User aggregates and compute averages asynchronously.
- Shared rides / pooling: Model a Ride as a composition that can include multiple riders, or create a PoolRide subclass.
Concurrency and scaling notes (quick)
- Matching: Use spatial indices (geohash/quadtrees) and an event-driven queue for driver notifications.
- Consistency: Use optimistic locking or distributed locks when assigning drivers to avoid double-assign.
- Events: Emit events (RideStarted, RideCompleted, PaymentProcessed) so other services (analytics, notifications) can react asynchronously.
Common interview pitfalls
- Overloading Ride or Driver with too many responsibilities (payment logic, notification delivery, complex matching) — explain why you separate concerns.
- Forgetting invalid state transitions — show you validated allowed moves.
- Not discussing failures (what happens if payment fails or driver cancels at last minute).
Quick checklist to present in an interview
- List the 5 classes and their responsibilities.
- Explain ride state transitions and where you enforce them.
- Describe how matching works at a high level and how you avoid race conditions.
- Show how Payment is decoupled and how pricing/surge can be added.
- Call out testability and extension points (strategies, policies, events).
With this concise model and talking points you can clearly explain an OOD for Uber in interviews: 3 core entities (User/Driver/Ride) and 2 coordinators (RideManager/Payment), with a focus on responsibilities, valid state transitions, and easy extensibility.


