When I started practicing Low-Level Design, I realized that jumping directly into drawing UML diagrams often leads to messy, confusing designs.
So instead, I followed a step-by-step approach—breaking the system into components, understanding flow, and then translating that into a clean UML.
Here’s how I approached designing a simplified Uber/Ola system.
🎯 Problem Statement
Design a ride-booking system that can:
Create a trip (source → destination)
Match a driver
Calculate price
Support extensibility (new pricing logic, ride types, etc.)
Step 1: Identify Core Components & Actors
Start simple. Who are the main actors?
Rider → requests a trip
Driver → fulfills the trip
These are the foundation of the system. Everything else builds around them.
Step 2: Define Features & Assumptions
To avoid overcomplicating early design, I made a few assumptions:
Each driver has one vehicle
One rider books one trip at a time
We focus only on trip creation flow
👉 This helps narrow scope and focus on correctness over completeness.
Step 3: Outline the Logical Flow (Client Perspective)
Instead of jumping into classes, I first defined the end-to-end flow from client side:
Rider enters source, destination, rideType
↓
RideBookingSystem receives request
↓
RideService orchestrates flow
↓
RideFactory → creates Bike / Auto / Cab
↓
StrategyMgr → selects pricing & matching strategy
↓
Driver is matched
↓
Price is calculated
↓
Trip is created
👉 This step is crucial—it directly drives your class design.
Step 4: Identify Classes & Responsibilities
Core Entities
Trip → represents a ride
Rider / Driver → system users
TripMetaData → context (ratings, locations)
Manager Classes (to organize logic)
TripMgr → manages trip lifecycle
RiderMgr / DriverMgr → manage users
StrategyMgr → selects strategies
👉 These act as centralized handlers for data and operations.
Step 5: Define Relationships
Understanding relationships is key to a good UML.
Aggregation (loosely coupled)
RiderMgr → Rider
DriverMgr → Driver
👉 These objects can exist independently.
Composition (tightly coupled)
TripMgr → Trip
👉 Trip lifecycle is controlled by TripMgr.
Step 6: Apply Design Patterns
To make the system extensible:
🔹 Singleton Pattern
Used for:
- TripMgr
- RiderMgr
- DriverMgr
👉 Ensures a single source of truth as these classes will be interacting with database.
🔹 Strategy Pattern
Used for:
- Pricing Strategy
- Driver Matching Strategy
PricingStrategy → Default / RatingBased
DriverMatchingStrategy → LeastTimeBased
👉 This avoids hardcoding logic and allows easy extension.
🔹 Factory Pattern (Ride Selection)
To support multiple ride types:
RideFactory → BikeRide / AutoRide / CabRide
Flow:
- User selects ride type
- RideService delegates creation to RideFactory
- Factory returns appropriate ride type object

👉 This centralizes ride creation and avoids scattered conditional logic.
Have attached a rough UML diagram for reference
This exercise helped me:
Think in terms of flow before code
Understand where Factory vs Strategy fits
Appreciate the importance of clear system boundaries
Still learning—would love to hear how others approach this problem 🙌
This article was originally published by DEV Community and written by Srishti Prasad.
Read original article on DEV Community