Why diagrams matter in LLD interviews
UML (Unified Modeling Language) is a standard visual notation for describing software structure and behaviour. It defines over a dozen diagram types, but LLD interviews use only a handful, and mostly one: the class diagram, which shows classes, their fields and methods, and how they relate.
Why draw at all when you could just write code? Three reasons:
- Speed of agreement. A rough diagram takes five minutes and lets the interviewer say "that's not what I meant" before you have written 300 lines.
- It exposes design flaws early. A class with fifteen arrows coming out of it is a god class. A cycle of arrows hints at tight coupling. You see these at a glance in a diagram, but not in code.
- It is the shared vocabulary. "Floor composes Spots, one-to-many" is precise and quick. Interviewers expect you to know what a filled diamond means.
Interviewers rarely penalise imperfect notation, but they do penalise confused relationships — for example drawing inheritance where composition belongs. This lesson teaches the notation in text form, because in many interviews you will be typing into a shared document rather than drawing with a mouse. Every relationship is paired with the Java code it represents, so you can move between diagram and code in both directions.
Class diagram notation
A class is drawn as a box with three compartments: name, attributes (fields), and operations (methods).
+-----------------------------------+
| ParkingSpot | <- class name
+-----------------------------------+
| - id: String | <- attributes
| - size: SpotSize |
| - occupied: boolean |
| # floorNo: int |
+-----------------------------------+
| + isFree(): boolean | <- operations
| + assign(v: Vehicle): void |
| + release(): void |
| - validate(v: Vehicle): void |
+-----------------------------------+
Visibility symbols
| Symbol | Meaning | Java keyword |
|---|---|---|
+ | Public | public |
- | Private | private |
# | Protected | protected |
~ | Package | (no modifier) |
Attribute and method syntax
- Attribute:
visibility name: Type [multiplicity] = default. Example:- tags: String[0..*]or- count: int = 0. - Method:
visibility name(param: Type, ...): ReturnType. Example:+ park(v: Vehicle): Ticket. - Static members are underlined in drawn UML. In text, write
{static}or prefix with$; the important thing is to be consistent and say what you mean. - Abstract classes and methods are written in italics in drawn UML. In text, write
<<abstract>>above the name or{abstract}after a method. - Interfaces are marked with the stereotype
<<interface>>above the name. A stereotype is a label in guillemets (<< >>) that adds a category to an element;<<enum>>and<<abstract>>are other common ones.
+-------------------------+ +-------------------------+
| <<interface>> | | <<enum>> |
| PricingStrategy | | SpotSize |
+-------------------------+ +-------------------------+
| + fee(t: Ticket): Money | | SMALL |
+-------------------------+ | MEDIUM |
| LARGE |
+-------------------------+
In an interview you will rarely write every getter. Show the fields that define the class and the methods that matter for the use cases. Leave out boilerplate.
Relationships
UML has seven relationships you need. They differ in strength — how tightly two classes are bound — and the arrowheads differ accordingly. From weakest to strongest:
Dependency A - - - - - - > B dashed, open arrow
Association A ------------- B solid line
Directed assoc. A ------------> B solid, open arrow
Aggregation A <>----------- B hollow diamond at whole
Composition A <#>---------- B filled diamond at whole
Generalisation A ------------|> B solid, hollow triangle
Realisation A - - - - - -|> B dashed, hollow triangle
In text we write a hollow diamond as <> and a filled diamond as <#> (some people use <*>). State your convention once at the top of your diagram and the interviewer will follow it.
Multiplicity
Multiplicity says how many instances on one end relate to one instance on the other end. It is written near the end of the line it describes.
| Notation | Meaning | Example |
|---|---|---|
1 | Exactly one | A ticket has exactly 1 spot |
0..1 | Zero or one | A spot has 0..1 current vehicle |
* or 0..* | Zero or more | A user has * bookings |
1..* | One or more | An order has 1..* items |
2..4 | A specific range | A table seats 2..4 people |
Reading rule: the number sits next to the class it counts. In Floor 1 <#>---- 1..* Spot, read "one floor has one or more spots" and "each spot belongs to exactly one floor".
1. Association
Association is a structural link: objects of one class know about objects of another, usually through a field, and both live independently. A plain line means the link can be navigated both ways (or the direction is unspecified).
+---------+ * * +---------+
| Student |-------------------| Course |
+---------+ enrolled in +---------+
"A student enrols in many courses; a course has many students." A many-to-many association in code usually means collections on one or both sides:
import java.util.HashSet;
import java.util.Set;
class Student {
private final String name;
private final Set<Course> courses = new HashSet<>();
Student(String name) { this.name = name; }
void enrol(Course c) {
courses.add(c);
c.addStudent(this); // keep both sides in sync
}
String name() { return name; }
}
class Course {
private final String code;
private final Set<Student> students = new HashSet<>();
Course(String code) { this.code = code; }
void addStudent(Student s) { students.add(s); }
int size() { return students.size(); }
}
A two-way association has a cost: both sides must be kept consistent. When only one direction is needed, use a directed association instead.
When the link itself has data — the grade a student got in a course, the date of enrolment — model an association class (here Enrolment), which becomes a real class with references to both ends.
2. Directed association
A directed association (navigable in one direction only) means A holds a reference to B, but B does not know about A.
+-------+ 1 1 +-------------+
| Order |---------------->| Customer |
+-------+ +-------------+
"An order knows its customer; the customer class does not keep a list of orders." This is the most common relationship in practice, because one-way references are simpler.
class Customer {
final String id;
Customer(String id) { this.id = id; }
}
class Order {
private final Customer customer; // navigable Order -> Customer only
Order(Customer customer) { this.customer = customer; }
Customer customer() { return customer; }
}
3. Aggregation
Aggregation is a whole-part association where the part can exist independently of the whole and may be shared. The hollow diamond sits at the whole.
+------------+ 1 * +----------+
| Department |<>-------------| Professor|
+------------+ +----------+
"A department has many professors; professors exist independently and can move to another department."
import java.util.ArrayList;
import java.util.List;
class Professor {
final String name;
Professor(String name) { this.name = name; }
}
class Department {
private final List<Professor> members = new ArrayList<>();
// Parts are created elsewhere and passed in.
void add(Professor p) { members.add(p); }
void remove(Professor p) { members.remove(p); }
}
Aggregation is weakly defined
The UML specification itself leaves aggregation's meaning loose, and many practitioners treat it as just an association. In interviews, use it only when you want to stress "has-a, but parts are independent and shareable". If unsure, a plain association is never wrong.
4. Composition
Composition is strong ownership: the part belongs to exactly one whole at a time, and its lifetime is tied to the whole. Delete the whole and the parts go with it. The filled diamond sits at the whole, and the whole's multiplicity is always 1 (or 0..1).
+-----------+ 1 1..* +-----------+
| Order |<#>--------------| OrderLine |
+-----------+ +-----------+
"An order is made of one or more order lines; a line cannot exist without its order and never belongs to two orders."
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class OrderLine {
final String sku;
final int qty;
final long unitPricePaise;
OrderLine(String sku, int qty, long unitPricePaise) {
this.sku = sku;
this.qty = qty;
this.unitPricePaise = unitPricePaise;
}
long subtotal() { return qty * unitPricePaise; }
}
class PurchaseOrder {
private final List<OrderLine> lines = new ArrayList<>();
// The whole creates its parts; callers pass data, not OrderLine objects.
void addLine(String sku, int qty, long unitPricePaise) {
lines.add(new OrderLine(sku, qty, unitPricePaise));
}
long total() {
long t = 0;
for (OrderLine l : lines) t += l.subtotal();
return t;
}
List<OrderLine> lines() { return Collections.unmodifiableList(lines); }
}
The code signals composition by having the whole create its parts and never accepting an externally created part. Exposing them read-only keeps ownership clear.
5. Dependency
A dependency is the weakest relationship: A uses B temporarily — as a method parameter, a local variable, or a return type — but does not store it in a field. A change to B may require a change to A.
+------------------+ +---------+
| InvoicePrinter |- - - - - ->| Order |
+------------------+ uses +---------+
class Invoice {
final String orderId;
final long totalPaise;
Invoice(String orderId, long totalPaise) { this.orderId = orderId; this.totalPaise = totalPaise; }
}
class InvoicePrinter {
// Invoice is only a parameter: a dependency, not an association.
String print(Invoice invoice) {
return "Invoice " + invoice.orderId + ": " + invoice.totalPaise + " paise";
}
}
Rule of thumb: field means association; parameter or local variable means dependency.
6. Generalisation (inheritance)
Generalisation is the is-a relationship between a subclass and a superclass. The hollow triangle points to the parent.
+----------------+
| <<abstract>> |
| Vehicle |
+----------------+
| - plate: String|
+-------^--------+
|
+-----------+-----------+
| | |
+---------+ +---------+ +---------+
| Bike | | Car | | Truck |
+---------+ +---------+ +---------+
abstract class Vehicle {
private final String plate;
protected Vehicle(String plate) { this.plate = plate; }
String plate() { return plate; }
abstract int wheels();
}
class Bike extends Vehicle {
Bike(String plate) { super(plate); }
int wheels() { return 2; }
}
class Car extends Vehicle {
Car(String plate) { super(plate); }
int wheels() { return 4; }
}
class Truck extends Vehicle {
Truck(String plate) { super(plate); }
int wheels() { return 6; }
}
Drawing three subclasses joined to one triangle (as above) is the tidy convention for several children.
7. Realisation (implementing an interface)
Realisation means a class implements the contract of an interface. It is drawn with a dashed line and a hollow triangle pointing to the interface.
+---------------------+
| <<interface>> |
| PaymentStrategy |
+---------------------+
| + pay(amt: long) |
+----------^----------+
:
+--------+--------+
: :
+-------------+ +-------------+
| UpiPayment | | CardPayment |
+-------------+ +-------------+
interface PaymentStrategy {
boolean pay(long amountPaise);
}
class UpiPayment implements PaymentStrategy {
public boolean pay(long amountPaise) { return amountPaise > 0; }
}
class CardPayment implements PaymentStrategy {
public boolean pay(long amountPaise) { return amountPaise > 0; }
}
Some people draw an interface as a small circle ("lollipop") attached to the class. Either form is acceptable.
Relationships summary
| Relationship | Text arrow | Meaning | Java clue |
|---|---|---|---|
| Dependency | - - -> | Uses temporarily | Parameter, local variable, return type |
| Association | ----- | Knows about, both ways | Fields on both sides |
| Directed association | ----> | Knows about, one way | Field on one side |
| Aggregation | <>--- | Has-a, parts independent | Part passed in, may be shared |
| Composition | <#>-- | Owns, shared lifetime | Whole creates parts, never shares |
| Generalisation | ---|> | Is-a (class) | extends |
| Realisation | - -|> | Implements a contract | implements |
Common mistake
Putting the diamond on the wrong end. The diamond always sits next to the whole (the container), never the part. In Library <#>---- Book, the library is the whole. A quick self-check: read it aloud as "Library has Books".
Interview tip
You do not need to defend aggregation versus association for every line. Spend your precision on the relationships that drive design: composition (lifetimes and ownership), generalisation vs realisation (inheritance vs interface), and multiplicity (which drives whether a field is a single reference or a collection).
Sequence diagrams
A class diagram is static: it shows structure. A sequence diagram shows behaviour: which objects call which, in what order, for one use case. Time flows downwards.
Elements:
- Lifeline: a vertical line for each participating object, headed with
name: Classor just the class. - Message: a horizontal arrow from caller to callee, labelled with the method call. A solid arrow is a call; a dashed arrow is a return.
- Activation bar: a thin box on a lifeline showing the object is executing. In text, it is usually omitted.
- Fragments: boxes labelled
alt(if/else),opt(optional),looparound parts of the interaction.
Here is the "park a vehicle" use case:
Driver Gate ParkingLot SpotAllocator Spot Ticket
| | | | | |
|enter(v)| | | | |
|------->| park(v) | | | |
| |------------->| find(v.size) | | |
| | |------------->| | |
| | | spot | | |
| | |<- - - - - - -| | |
| +------+--------------+--------------+----------+-------+ |
| | alt [spot found] | |
| | | | assign(v) | | | |
| | | |------------------------>| | |
| | | | new Ticket(spot, v, now) | |
| | | |-------------------------------------->
| | | ticket | | | | |
| | |<- - - - - - -| | | | |
| +- - - [else] - - - - - - - - - - - - - - - - - - - - - + |
| | | LotFullException | | | |
| | |<- - - - - - -| | | | |
| +------+--------------+--------------+----------+-------+ |
| ticket | | | | |
|<- - - -| | | | |
Read it top to bottom: the gate asks the lot to park; the lot asks the allocator for a spot; if one is found, it assigns the vehicle and creates a ticket; otherwise it throws. The diagram reveals design decisions: allocation is delegated to SpotAllocator (a Strategy), and ParkingLot creates the Ticket.
The same flow in code, which should match the diagram line by line:
import java.time.Instant;
import java.util.List;
import java.util.Optional;
enum Size { SMALL, LARGE }
class Spot {
final String id;
final Size size;
private String plate;
Spot(String id, Size size) { this.id = id; this.size = size; }
boolean isFree() { return plate == null; }
void assign(String plate) { this.plate = plate; }
}
record Ticket(String spotId, String plate, Instant entry) {}
interface SpotAllocator {
Optional<Spot> find(List<Spot> spots, Size size);
}
class FirstFreeAllocator implements SpotAllocator {
public Optional<Spot> find(List<Spot> spots, Size size) {
return spots.stream().filter(s -> s.isFree() && s.size == size).findFirst();
}
}
class ParkingLot {
private final List<Spot> spots;
private final SpotAllocator allocator;
ParkingLot(List<Spot> spots, SpotAllocator allocator) {
this.spots = spots;
this.allocator = allocator;
}
synchronized Ticket park(String plate, Size size) {
Spot spot = allocator.find(spots, size)
.orElseThrow(() -> new IllegalStateException("lot full"));
spot.assign(plate);
return new Ticket(spot.id, plate, Instant.now());
}
}
class SequenceDemo {
public static void main(String[] args) {
ParkingLot lot = new ParkingLot(List.of(new Spot("S1", Size.SMALL)), new FirstFreeAllocator());
System.out.println(lot.park("KA01AB1234", Size.SMALL).spotId()); // S1
try {
lot.park("KA02CD5678", Size.SMALL);
} catch (IllegalStateException e) {
System.out.println(e.getMessage()); // lot full
}
}
}
park is synchronized so two gates cannot grab the same free spot between find and assign. A sequence diagram is a good place to point at that gap and say so.
Interview tip
Draw a sequence diagram for the single most important or most concurrent use case — booking a seat, dispensing cash — not for every use case. It is the clearest way to show where locking happens and which class owns each step.
Use-case diagrams (briefly)
A use-case diagram shows actors (users or external systems) and the use cases they can perform, inside a system boundary. It is a requirements tool, useful at the start of an interview to confirm scope.
+--------------------------------+
| Library System |
Member | |
O ---------+--> ( Search catalogue ) |
/|\ --------+--> ( Borrow book ) |
/ \ --------+--> ( Return book ) |
| : |
| : <<include>> |
| v |
| ( Calculate fine ) |
Librarian | |
O ---------+--> ( Add book ) |
/|\ --------+--> ( Block member ) |
/ \ | |
+--------------------------------+
<<include>> means one use case always runs another as part of it (returning a book always calculates the fine, even if zero). <<extend>> means an optional addition that happens only under some condition. In an interview, a plain bulleted list of actors and use cases conveys the same information and is often faster.
Activity diagrams (briefly)
An activity diagram is a flowchart of a process: actions, decisions (diamonds), and parallel branches (fork and join bars). Use it when a use case has significant branching, such as checkout.
( start )
|
[ Validate cart ]
|
< in stock? > --no--> [ Show error ] --> ( end )
| yes
[ Apply coupon ]
|
[ Charge payment ]
|
< success? > --no--> [ Release stock ] --> ( end )
| yes
======== fork ========
| |
[ Send SMS ] [ Create invoice ]
| |
======== join ========
|
( end )
The fork bar means both actions run in parallel (or in any order); the join waits for both.
State diagrams (briefly)
A state machine diagram shows the states an object can be in and the events that move it between states. It is extremely useful for LLD problems like vending machines, elevators and order lifecycles, and it maps directly to the State pattern in behavioural patterns.
place() pay() ship()
(start)-------> [CREATED] -------> [PAID] -------> [SHIPPED]
| | |
cancel() | cancel() | deliver()|
v v v
[CANCELLED] <----- (refund) [DELIVERED]
Each arrow is labelled event [guard] / action, for example pay() [amount == total] / reserveStock. Any event not shown from a state is invalid in that state — for example ship() from CREATED must be rejected. Writing that rule down early prevents bugs.
import java.util.Map;
import java.util.Set;
enum OrderStatus {
CREATED, PAID, SHIPPED, DELIVERED, CANCELLED;
private static final Map<OrderStatus, Set<OrderStatus>> ALLOWED = Map.of(
CREATED, Set.of(PAID, CANCELLED),
PAID, Set.of(SHIPPED, CANCELLED),
SHIPPED, Set.of(DELIVERED),
DELIVERED, Set.of(),
CANCELLED, Set.of());
boolean canMoveTo(OrderStatus next) {
return ALLOWED.get(this).contains(next);
}
}
class StateDemo {
public static void main(String[] args) {
System.out.println(OrderStatus.CREATED.canMoveTo(OrderStatus.SHIPPED)); // false
System.out.println(OrderStatus.PAID.canMoveTo(OrderStatus.SHIPPED)); // true
}
}
| Diagram | Shows | Use it in an interview when |
|---|---|---|
| Class | Static structure | Always — it is the core artefact |
| Sequence | Calls over time for one scenario | Explaining the main or the most concurrent flow |
| Use case | Actors and goals | Confirming scope at the start |
| Activity | Process flow with branches | A use case with many decisions or parallel steps |
| State | Lifecycle of one object | Vending machine, elevator, order, booking status |
Worked example: a library management system
Let us build a complete class diagram from a problem statement, step by step.
Problem. Members can search for books by title or author, borrow up to a limit of copies, and return them. Each book can have several physical copies. Late returns incur a fine per day. Librarians add books and copies. Members are notified when a reserved book becomes available.
Step 1: Find candidate classes
Nouns: member, book, title, author, copy, limit, fine, day, librarian, reservation, notification, catalogue.
- Book (the title as a work: ISBN, title, authors) and BookCopy (a physical item with a barcode and status) are different things. Separating them is the key modelling insight for this problem — many candidates miss it.
- Member and Librarian are both people with accounts: an abstract Account parent.
- Loan records which member has which copy, with dates. This is an association class between member and copy.
- Reservation records a member waiting for a book.
- Catalogue handles search.
- FinePolicy is a Strategy because fine rules may change.
- Notifier is an interface for notification channels.
- CopyStatus is an enum: AVAILABLE, LOANED, RESERVED, LOST.
Step 2: Decide relationships and multiplicities
- Book 1
<#>1..* BookCopy — a copy has no meaning without its book record. - Member 1 ---- 0..* Loan, and Loan * ----> 1 BookCopy — each loan is for exactly one copy.
- Member 1 ---- 0..* Reservation, and Reservation * ----> 1 Book (you reserve a title, not a specific copy).
- Catalogue 1
<>* Book — books are registered in the catalogue. - Account is generalised by Member and Librarian.
- LibraryService depends on FinePolicy and Notifier (interfaces, realised by concrete classes).
Step 3: Draw it
Legend: <#> composition, <> aggregation, ---> navigable
association; the triangle-headed line is extends
+-------------------+
| <<abstract>> |
| Account |
+-------------------+
| - id: String |
| - name: String |
+---------^---------+
|
+------------+------------+
| |
+-------------------+ +-------------------+
| Member | | Librarian |
+-------------------+ +-------------------+
| - maxLoans: int | | + addBook(...) |
| + canBorrow() | +-------------------+
+-------------------+
| 1 | 1
| |
| 0..* | 0..*
+---------------+ +----------------+
| Loan | | Reservation |
+---------------+ +----------------+
| - issued | | - created |
| - due | +-------+--------+
| - returned | |
| + daysLate() | |
+-------+-------+ |
| * | *
v 1 v 1
+---------------+ 1..* 1 +-------------+
| BookCopy |------<#>| Book |
+---------------+ +-------------+
| - barcode | | - isbn |
| - status | | - title |
+---------------+ | - authors |
+------+------+
| *
|
<> 1
+-------------+
| Catalogue |
+-------------+
| + search(q) |
+-------------+
+------------------------+ +------------------------+
| LibraryService |- - ->| <<interface>> |
+------------------------+ | FinePolicy |
| + borrow(m, isbn): Loan| +------------------------+
| + giveBack(l): long | | + fine(l: Loan): long |
| + reserve(m, isbn) | +-----------^------------+
+------------------------+ :
: +----------+-----------+
: | PerDayFinePolicy |
v +----------------------+
+------------------------+
| <<interface>> |
| Notifier |
+------------------------+
To keep each diagram under phone width, the service layer is split into a second diagram. Doing the same on a whiteboard — entities in one picture, services and strategies in another — also makes each picture easier to read.
Step 4: Check the diagram against use cases
- Search:
Catalogue.searchreturnsBooks; the caller can then check copies' statuses. Covered. - Borrow:
LibraryService.borrow(member, isbn)checksmember.canBorrow(), finds an availableBookCopyof the book, marks itLOANED, creates aLoan. Covered. - Return:
giveBack(loan)sets the return date, asksFinePolicyfor the fine, marks the copyAVAILABLE(orRESERVEDif someone is waiting) and notifies viaNotifier. Covered. - Concurrency: two members borrowing the last copy — the borrow step must find and mark the copy atomically (for example, synchronising on the
Book). Noted.
Step 5: Translate the core into code
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
enum CopyStatus { AVAILABLE, LOANED, RESERVED, LOST }
class BookCopy {
final String barcode;
CopyStatus status = CopyStatus.AVAILABLE;
BookCopy(String barcode) { this.barcode = barcode; }
}
class Book {
final String isbn;
final String title;
private final List<BookCopy> copies = new ArrayList<>(); // composition
Book(String isbn, String title) { this.isbn = isbn; this.title = title; }
void addCopy(String barcode) { copies.add(new BookCopy(barcode)); }
synchronized Optional<BookCopy> checkOutAnyCopy() {
for (BookCopy c : copies) {
if (c.status == CopyStatus.AVAILABLE) {
c.status = CopyStatus.LOANED;
return Optional.of(c);
}
}
return Optional.empty();
}
}
class Member {
final String id;
final int maxLoans;
int activeLoans;
Member(String id, int maxLoans) { this.id = id; this.maxLoans = maxLoans; }
boolean canBorrow() { return activeLoans < maxLoans; }
}
class Loan {
final Member member;
final BookCopy copy;
final LocalDate due;
LocalDate returned;
Loan(Member member, BookCopy copy, LocalDate due) { this.member = member; this.copy = copy; this.due = due; }
long daysLate() {
return returned == null ? 0 : Math.max(0, ChronoUnit.DAYS.between(due, returned));
}
}
interface FinePolicy {
long finePaise(Loan loan);
}
class PerDayFinePolicy implements FinePolicy {
private final long perDayPaise;
PerDayFinePolicy(long perDayPaise) { this.perDayPaise = perDayPaise; }
public long finePaise(Loan loan) { return loan.daysLate() * perDayPaise; }
}
class LibraryService {
private final FinePolicy finePolicy;
LibraryService(FinePolicy finePolicy) { this.finePolicy = finePolicy; }
Optional<Loan> borrow(Member m, Book book, LocalDate today) {
if (!m.canBorrow()) return Optional.empty();
return book.checkOutAnyCopy().map(copy -> {
m.activeLoans++;
return new Loan(m, copy, today.plusDays(14));
});
}
long giveBack(Loan loan, LocalDate today) {
loan.returned = today;
loan.copy.status = CopyStatus.AVAILABLE;
loan.member.activeLoans--;
return finePolicy.finePaise(loan);
}
}
class LibraryDemo {
public static void main(String[] args) {
Book book = new Book("9780134685991", "Effective Java");
book.addCopy("C-1");
Member m = new Member("M-1", 2);
LibraryService lib = new LibraryService(new PerDayFinePolicy(1000));
LocalDate issued = LocalDate.of(2026, 10, 1);
Loan loan = lib.borrow(m, book, issued).orElseThrow();
System.out.println("due " + loan.due); // due 2026-10-15
System.out.println(lib.borrow(new Member("M-2", 2), book, issued).isPresent()); // false
System.out.println("fine " + lib.giveBack(loan, LocalDate.of(2026, 10, 18))); // fine 3000
}
}
Check the fine: issued 1 October, due 14 days later on 15 October, returned 18 October, so 3 days late × 1000 paise = 3000 paise (₹30).
Notice how each diagram element became code: the composition diamond became Book creating BookCopy objects internally; the FinePolicy realisation became an interface plus class; the dependency from LibraryService became a constructor parameter.
Reviewing a diagram before you code
Once a diagram exists, spend one minute checking it against a short list. Each check catches a design flaw that is expensive to fix after the code is written.
- Can every use case be traced? Pick each use case and follow it through the boxes. If "return book" needs information no class holds (for example, the due date), a field or class is missing.
- Does any class have far more arrows than the rest? A class with many outgoing lines usually does too much. Move some responsibility to a collaborator.
- Are there cycles? If A points to B, B to C and C back to A, a change anywhere ripples everywhere. Often one of the links can become one-directional, or an interface can break the cycle.
- Is every inheritance line a true behavioural is-a? If a subclass would have to throw or ignore a parent method, replace the triangle with composition or a smaller interface.
- Are variable behaviours behind realisation lines? Pricing, fines, allocation and notification rules should point at interfaces, not concrete classes, if the problem says they may change.
- Do multiplicities match the requirements? If the problem says "a member can borrow up to five books", the diagram should say
0..5or carry amaxLoansfield, and the code must enforce it. - Is shared mutable state visible? Mark the objects many users touch at once (a copy's status, a seat, a spot). Those are where locks go.
Going from code back to a diagram
Interviewers sometimes show you code and ask you to draw the diagram, or ask you to explain an existing codebase. Work in this order:
- Each class or interface becomes a box. Put
<<interface>>,<<abstract>>or<<enum>>on top where relevant. extendsbecomes a solid triangle line;implementsbecomes a dashed triangle line.- Each field whose type is another of your classes becomes an association. A collection field (
List<Spot>) means multiplicity*on the far end. Then ask who creates the referenced objects: if the owner creates them in its own methods and never shares them, upgrade the line to composition. - Each class used only as a parameter, local variable or return type becomes a dependency.
- Skip library types such as
String,ListandLocalDate; show them as attribute types instead of boxes.
This mechanical translation is also how many tools reverse-engineer diagrams from code. Being able to do it by hand shows you understand that the diagram and the code are two views of the same design, not separate artefacts that can drift apart.
Interview questions
Q1. What is the difference between aggregation and composition?
Both are whole-part relationships. In aggregation, parts can exist without the whole and may be shared, like professors in a department. In composition, the whole owns its parts exclusively and their lifetime is bound to it, like order lines in an order. In code, composition shows up as the whole creating its parts internally and never handing them to another whole.
Q2. How do you tell association from dependency in code?
If class A stores a reference to B in a field, it is an association (directed if B does not reference A). If A only uses B as a method parameter, local variable or return type, it is a dependency. Dependencies are weaker because the link exists only during a method call.
Q3. What is the difference between generalisation and realisation?
Generalisation is class inheritance (extends), drawn with a solid line and a hollow triangle to the parent. Realisation is implementing an interface (implements), drawn with a dashed line and a hollow triangle to the interface. One inherits implementation and state; the other only promises to fulfil a contract.
Q4. What does multiplicity 0..1 vs 1 imply for your code?
1 means the reference is mandatory, so it should be set in the constructor and never null. 0..1 means it is optional, so the field may be null or, better, exposed as an Optional. Multiplicities with * mean a collection field.
Q5. Where does the diamond go in an aggregation or composition?
Always at the whole (the container), not at the part. Order <#>---- OrderLine means an order is composed of lines. Reading it as "Order has OrderLines" is a quick check.
Q6. When would you draw a sequence diagram in an LLD interview?
For the most important or most concurrency-sensitive flow, such as booking a seat or parking a vehicle. It shows which class owns each step, where the critical section is, and how errors propagate. Drawing one for every use case wastes time.
Q7. How would you model the relationship between a student, a course and a grade?
Student and course are many-to-many, and the grade belongs to the pair, not to either side alone. I would introduce an association class, Enrolment, with references to one student and one course plus the grade and date. Each student and each course then has many enrolments.
Q8. Why separate Book and BookCopy in a library design?
A book is the abstract work (ISBN, title, authors); a copy is a physical item that can be loaned, lost or reserved. Loans and statuses belong to copies, while search and reservations usually target books. Merging them forces you to duplicate title data across copies or makes per-copy status impossible.
Q9. What is a state diagram and how does it help in LLD?
It shows the states an object can be in and the events that move it between them, with guards and actions. It makes illegal transitions explicit, such as shipping an unpaid order. It maps directly to an enum transition table or to the State pattern in code.
Q10. What do <<include>> and <<extend>> mean in a use-case diagram?
<<include>> means one use case always invokes another as part of its flow, like "return book" always running "calculate fine". <<extend>> means an optional behaviour that adds to a base use case only under a condition, like "apply discount" extending "checkout" when a coupon is present.
Q11. Is it worth drawing every getter and setter in a class diagram?
No. Show identifying fields and the methods that support the use cases. Boilerplate clutters the diagram and hides the design decisions the interviewer wants to discuss.
Q12. How do you show an abstract class and an interface in text UML?
Put a stereotype above the name: <<abstract>> for abstract classes and <<interface>> for interfaces. Mark abstract methods with {abstract}. Use a solid triangle line for extending the abstract class and a dashed triangle line for implementing the interface.
Key takeaways
- The class diagram is the core LLD artefact; sequence and state diagrams are the useful extras.
- Class boxes have name, attributes and operations, with
+ - # ~for visibility. - Seven relationships, weakest to strongest: dependency, association, directed association, aggregation, composition; plus generalisation and realisation for type hierarchies.
- Field means association; parameter or local means dependency; whole-creates-part means composition.
- The diamond goes at the whole; the triangle points to the parent or interface.
- Multiplicity decides whether a field is mandatory, optional or a collection.
- Draw a sequence diagram for the most concurrent flow to show where locking belongs.
- Keep diagrams small and split them by layer; precision on key relationships beats completeness.
Next lesson
Continue with Creational design patterns.

