Low-Level Design: Understanding the Basics
When building a software application, we don't just need to decide what the system should do. We also need to decide how the individual parts of the system will be designed and work together.
This is where Low-Level Design (LLD) comes in.
What is Low-Level Design?
Low-Level Design (LLD) is the process of designing the internal structure of an application.
It focuses on things like:
- Classes and objects
- Relationships between objects
- Responsibilities of each class
- Data flow
- Interfaces
- Implementation decisions
- Code structure
For example, imagine we are building a food delivery application.
At the LLD level, we might identify classes such as:
User
Restaurant
Food
Order
Payment
DeliveryPartner
We then decide what each class should do and how they interact.
For example:
User → places → Order
Order → contains → Food
Order → uses → Payment
Order → assigned to → DeliveryPartner
The goal is to create a design where each component has a clear responsibility and the system can be easily changed later.
What Should We Think About Before LLD?
Before jumping directly into classes and code, there are a few things we should identify.
1. Objects / Entities
First, identify the important entities in the problem.
For a Parking Lot system:
ParkingLot
Floor
ParkingSpot
Vehicle
Ticket
Payment
These entities will eventually become classes or components in our design.
2. Relationships
Next, understand how these objects interact.
For example:
ParkingLot
|
└── Floors
|
└── ParkingSpots
A ParkingLot contains multiple Floor objects, and each floor contains multiple ParkingSpot objects.
Understanding these relationships helps us decide whether we need composition, inheritance, association, or interfaces.
3. Data Security
We should also think about what data needs to be protected.
For example, in a banking application:
User
├── name
├── accountNumber
└── balance
We shouldn't allow every class to directly modify balance.
Instead, we can encapsulate it:
class Account {
private double balance;
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
}
This is one of the reasons encapsulation is important in LLD.
Core Goals of LLD
There are three important qualities we should aim for.
1. Scalability
Scalability means our design should be able to handle increasing requirements or usage without requiring a complete rewrite.
For example, suppose our payment system initially supports:
UPI
Later we want:
UPI
Credit Card
Debit Card
PayPal
A good design should allow us to add new payment methods without modifying a huge amount of existing code.
2. Maintainability
Maintainability means the code should be easy to understand, debug and modify.
Imagine having one class like:
OrderManager
that handles:
Order creation
Payment
Notifications
Delivery
Database operations
Changing one feature could break another.
Instead, we can separate responsibilities:
OrderService
PaymentService
NotificationService
DeliveryService
Now each component has a clearer responsibility.
3. Extensibility
Extensibility means we should be able to add new functionality with minimal changes to existing code.
For example, consider:
interface Payment {
void pay(double amount);
}
We can have:
UPIPayment
CardPayment
PayPalPayment
If we later introduce CryptoPayment, we can create another implementation without changing the existing payment classes.
This is where principles like SOLID and design patterns become very useful.
LLD vs HLD
A simple way to remember the difference is:
HLD decides what the system looks like. LLD decides how its components work internally.
High-Level Design
HLD deals with the overall architecture.
For example:
Client
↓
Load Balancer
↓
Application Servers
↓
Database
↓
Cache
Questions include:
- Should we use SQL or NoSQL?
- How many servers do we need?
- Do we need caching?
- How do we scale the system?
- What technology should we use?
Low-Level Design
LLD goes inside the application server.
For example:
OrderService
|
├── Order
├── Payment
├── User
└── Notification
Questions include:
- What classes do we need?
- What should each class be responsible for?
- Which classes should interact?
- Should we use inheritance or composition?
- Which design pattern fits the problem?
- How can we make the code extensible?
A Simple Example
Suppose we need to design a vehicle rental system.
At the HLD level, we might think about:
Mobile App
↓
Backend
↓
Database
At the LLD level, we think about:
Vehicle
↑
├── Car
├── Bike
└── Truck
User
Rental
Payment
We then define how these objects interact and what responsibilities they have.
Final Takeaway
LLD is not just about writing classes.
It's about thinking carefully about how code should be structured before writing it.
A good LLD should aim for:
- Clear responsibilities
- Low coupling
- High cohesion
- Maintainability
- Extensibility
- Reusable components
- Clean relationships between objects
As I continue learning LLD, I'll be exploring OOP, SOLID principles, design patterns, UML diagrams, and real-world LLD problems and applying these concepts through code.
Good LLD is about making the right design decisions before the code becomes difficult to change.
Top comments (0)