UML Class Diagrams
UML class diagrams show the static structure of a system: what classes exist, what they contain, and how they relate to each other. In interviews, youβll sketch these during the first 5-10 minutes to plan your implementation.
Why this matters: A quick class diagram before coding helps you identify entities, relationships, and responsibilities. Interviewers also use them to evaluate your design. You donβt need full UML rigor β just enough to communicate structure clearly.
Prerequisites
- Class Design Approach β the process that produces these diagrams
- SOLID Principles β relationships should follow SOLID
Class Notation
A class box has three sections:
classDiagram
class ParkingLot {
-floors: List~Floor~
-strategy: ParkingStrategy
-billingService: BillingService
+park(vehicle) Ticket
+unpark(ticket) Receipt
+getAvailableSlots(type) int
-findSlot(vehicle) Slot
}
Visibility modifiers:
+public β accessible from anywhere-private β accessible only within the class#protected β accessible from subclasses~package-private (Java) β accessible within the package
Relationship Types
This is the core knowledge for interviews. Know these six:
classDiagram
class Association
class Aggregation
class Composition
class Inheritance
class Implementation
class Dependency
Association --> Aggregation
Aggregation o-- Composition
Composition *-- Inheritance
Inheritance <|-- Implementation
Implementation <|.. Dependency
Quick Reference Table
| Relationship | Arrow | Meaning | Lifetime | Example |
|---|---|---|---|---|
| Association | --> |
A uses B | Independent | ParkingLot uses ParkingStrategy |
| Aggregation | o-- |
A has B, B exists independently | B survives Aβs deletion | Department has Employees |
| Composition | *-- |
A owns B, B dies with A | B cannot exist without A | Order owns LineItems |
| Inheritance | <\|-- |
B is-a A | N/A | Car extends Vehicle |
| Implementation | <\|.. |
B implements A | N/A | TokenBucket implements RateLimitAlgorithm |
| Dependency | ..> |
A temporarily uses B | Momentary | Service uses Logger in a method |
Relationships in Practice
classDiagram
class ParkingLot {
-floors: List~Floor~
-strategy: ParkingStrategy
}
class Floor {
-slots: List~Slot~
-floorNumber: int
}
class Slot {
-slotId: String
-type: SlotType
-vehicle: Vehicle
+isAvailable() bool
}
class Vehicle {
-licensePlate: String
-type: VehicleType
}
class ParkingStrategy {
<<interface>>
+findSlot(floors, type) Slot
}
class NearestFirstStrategy {
+findSlot(floors, type) Slot
}
class Ticket {
-vehicle: Vehicle
-slot: Slot
-entryTime: Instant
}
ParkingLot *-- Floor
Floor *-- Slot
Slot --> Vehicle
ParkingLot --> ParkingStrategy
ParkingStrategy <|.. NearestFirstStrategy
ParkingLot ..> Ticket
Reading this diagram:
- ParkingLot owns Floors (composition: floors donβt exist without the lot)
- Floor owns Slots (composition: slots donβt exist without a floor)
- Slot references a Vehicle (association: vehicle exists independently)
- ParkingLot uses a ParkingStrategy (dependency injection)
- NearestFirstStrategy implements ParkingStrategy
- ParkingLot creates Tickets (dependency: short-lived interaction)
Multiplicity
Show how many objects participate in a relationship:
| Notation | Meaning |
|---|---|
1 |
Exactly one |
0..1 |
Zero or one (optional) |
* or 0..* |
Zero or many |
1..* |
One or many |
3..5 |
Three to five |
classDiagram
class Order {
-items: List~LineItem~
-customer: Customer
}
class LineItem {
-product: Product
-quantity: int
}
class Customer {
-orders: List~Order~
}
Customer "1" --> "*" Order
Order "1" *-- "1..*" LineItem
Drawing Class Diagrams in Interviews (Speed Tips)
In a 90-minute round, you have about 5 minutes for a diagram. Focus on:
- Entities first β Just the class names in boxes. No methods yet.
- Key relationships β Composition and inheritance. Skip trivial associations.
- Interfaces where strategies exist β Show the Strategy or State interface with 2 implementations.
- Skip getters/setters β Only show meaningful methods.
- Use short names β
ParkingStrategynotIParkingAllocationStrategyInterface.
Composition vs Aggregation: The Key Distinction
This confuses people. The question is: does the child survive if the parent is destroyed?
| Composition (filled diamond) | Aggregation (empty diamond) |
|---|---|
| Child dies with parent | Child lives independently |
| Parent creates and owns child | Parent references existing child |
Order owns LineItems |
Team has Players |
House owns Rooms |
Playlist has Songs |
| Implemented as: field created in constructor | Implemented as: field passed in or set |
In practice, most LLD designs use composition. Aggregation is rarer.
Common Mistakes
- Every relationship is a plain arrow β Distinguish between composition, inheritance, and dependency. They communicate different things.
- Too much detail β Donβt draw every field and every method. Focus on what communicates structure.
- Missing interfaces β If you have a Strategy or State pattern, the interface IS the relationship to show.
- Confusing direction β Arrows point FROM dependent TO dependency.
ParkingLot --> ParkingStrategymeans ParkingLot depends on ParkingStrategy.
When to Use vs When to Avoid
| Draw a Class Diagram When | Skip It When |
|---|---|
| 5+ entities with non-obvious relationships | System has 2-3 simple classes |
| Patterns like Strategy, State, Observer are involved | Itβs a straightforward CRUD system |
| Interviewer asks for it | Youβre running out of time β code first |
| You need to plan before coding | Relationships are obvious from code |
Interview Questions
-
βWhatβs the difference between aggregation and composition?β β Composition: part dies with the whole (Order deletes its LineItems). Aggregation: part exists independently (Department doesnβt delete Employees when dissolved).
-
βWhen do you use interfaces vs abstract classes?β β Interfaces for contracts that unrelated classes implement (Strategy). Abstract classes for shared implementation that related classes extend (Template Method).
-
βShow me the class diagram for your solution.β β Be ready to draw one quickly. Focus on entities, key relationships, and patterns used. 5-6 boxes with arrows is ideal.
See It in Action
Every LLD problem on this site starts with a class diagram. See Parking Lot and File System for clear examples.
Translating Diagrams to Code
The mapping from UML to code is mechanical. Once youβve drawn the diagram, the class skeletons write themselves.
// Composition (filled diamond): parent creates and owns child
public class Order {
private final List<LineItem> items = new ArrayList<>(); // created internally
public void addItem(Product product, int qty) {
items.add(new LineItem(product, qty)); // Order creates LineItems
}
}
// Association (plain arrow): references an external object
public class Slot {
private Vehicle occupant; // assigned externally, not created here
public void occupy(Vehicle v) { this.occupant = v; }
public void free() { this.occupant = null; }
}
// Implementation (dashed arrow): implements an interface
public class NearestFirstStrategy implements ParkingStrategy {
@Override
public Slot findSlot(List<Floor> floors, VehicleType type) { /* ... */ }
}
# Composition: parent owns child lifecycle
class Order:
def __init__(self):
self._items: list[LineItem] = [] # created and owned by Order
def add_item(self, product, qty):
self._items.append(LineItem(product, qty))
# Association: references external object
class Slot:
def __init__(self):
self._occupant: Vehicle | None = None # not owned
def occupy(self, vehicle: Vehicle):
self._occupant = vehicle
# Implementation: concrete class implements interface
class NearestFirstStrategy(ParkingStrategy):
def find_slot(self, floors, vehicle_type):
pass
// Composition: unique_ptr signals ownership
class Order {
vector<unique_ptr<LineItem>> items_; // owned, dies with Order
public:
void addItem(Product* product, int qty) {
items_.push_back(make_unique<LineItem>(product, qty));
}
};
// Association: raw pointer or reference (non-owning)
class Slot {
Vehicle* occupant_ = nullptr; // not owned
public:
void occupy(Vehicle* v) { occupant_ = v; }
void free() { occupant_ = nullptr; }
};
// Implementation
class NearestFirstStrategy : public ParkingStrategy {
public:
Slot* findSlot(vector<Floor>& floors, VehicleType type) override { /* ... */ }
};