Class and sequence diagrams
You'll draw your design before you code it. This module covers just enough UML for a whiteboard: the six relationships in a class diagram, and sequence diagrams that show one use case step by step.
1. The idea
Class diagrams
A box per class: name, the important fields, the important methods. Lines say how classes relate:
| Relationship | Notation | Means | Example |
|---|---|---|---|
| Association | A —— B | A refers to B; both live independently | Order → Customer |
| Aggregation | A ◇—— B | A groups Bs that can outlive it | Team ◇ Player |
| Composition | A ◆—— B | A owns Bs; they die with it | Order ◆ LineItem |
| Inheritance | B ——▷ A | B is an A | CardPayment ▷ Payment |
| Realisation | B - - -▷ A | B implements interface A | EmailChannel ▷ Channel |
| Dependency | A - - -> B | A uses B briefly (a parameter) | Order ⇢ TaxCalculator |
Add multiplicities where they matter (1, 0..1, 1..*, 0..*). In an interview, fields and methods can be partial: draw what the discussion needs.
Sequence diagrams
Objects across the top, time running down, one arrow per call, dashed arrows for returns. Draw one for the main use case; it's where missing methods and wrong responsibilities show up.
2. How it works
The checkout from the code below as a sequence diagram: each step is one message between objects.
3. Try it yourself
Name the relationship
For each pair, choose the relationship you'd draw.
4. In code
Each class is commented with the relationship it has in the diagram: association, composition, inheritance and dependency.
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class Customer: # Customer "1" --places--> "0..*" Order (association)
name: str
@dataclass(frozen=True)
class LineItem: # Order "1" <>-- "1..*" LineItem (composition: items die with the order)
sku: str
price: int
qty: int
class Payment(ABC): # Payment <|-- CardPayment (inheritance / realisation)
@abstractmethod
def describe(self) -> str: ...
class CardPayment(Payment):
def __init__(self, last4: str) -> None:
self.last4 = last4
def describe(self) -> str:
return f"card ending {self.last4}"
class TaxCalculator: # Order ..> TaxCalculator (dependency: used, not stored)
def tax(self, amount: int) -> int:
return amount * 8 // 100
class Order:
def __init__(self, customer: Customer) -> None:
self.customer = customer # association: a reference to an object that lives on its own
self.items: list[LineItem] = [] # composition: created and owned here
self.payment: Payment | None = None
def add_item(self, sku: str, price: int, qty: int) -> None:
self.items.append(LineItem(sku, price, qty))
def total(self, tax: TaxCalculator) -> int: # dependency: passed in for this call only
subtotal = sum(i.price * i.qty for i in self.items)
return subtotal + tax.tax(subtotal)
if __name__ == "__main__":
ana = Customer("Ana")
order = Order(ana)
order.add_item("keyboard", 5000, 1)
order.add_item("cable", 500, 2)
order.payment = CardPayment("4242")
print(f"{order.customer.name}: {len(order.items)} line items, total {order.total(TaxCalculator())}, paid by {order.payment.describe()}")
class Customer { constructor(readonly name: string) {} } // Customer "1" --places--> "0..*" Order (association)
interface LineItem { sku: string; price: number; qty: number; } // Order "1" <>-- "1..*" LineItem (composition)
abstract class Payment { abstract describe(): string; } // Payment <|-- CardPayment (inheritance)
class CardPayment extends Payment {
constructor(private last4: string) { super(); }
describe(): string { return `card ending ${this.last4}`; }
}
class TaxCalculator { // Order ..> TaxCalculator (dependency: used, not stored)
tax(amount: number): number { return Math.floor((amount * 8) / 100); }
}
class Order {
readonly items: LineItem[] = []; // composition: created and owned here
payment: Payment | null = null;
constructor(readonly customer: Customer) {} // association: a reference to an object that lives on its own
addItem(sku: string, price: number, qty: number): void { this.items.push({ sku, price, qty }); }
total(tax: TaxCalculator): number { // dependency: passed in for this call only
const subtotal = this.items.reduce((s, i) => s + i.price * i.qty, 0);
return subtotal + tax.tax(subtotal);
}
}
const order = new Order(new Customer("Ana"));
order.addItem("keyboard", 5000, 1);
order.addItem("cable", 500, 2);
order.payment = new CardPayment("4242");
console.log(`${order.customer.name}: ${order.items.length} line items, total ${order.total(new TaxCalculator())}, paid by ${order.payment.describe()}`);import java.util.ArrayList;
import java.util.List;
record Customer(String name) {} // Customer "1" --places--> "0..*" Order (association)
record LineItem(String sku, int price, int qty) {} // Order "1" <>-- "1..*" LineItem (composition)
abstract class Payment { // Payment <|-- CardPayment (inheritance)
abstract String describe();
}
class CardPayment extends Payment {
private final String last4;
CardPayment(String last4) {
this.last4 = last4;
}
String describe() {
return "card ending " + last4;
}
}
class TaxCalculator { // Order ..> TaxCalculator (dependency: used, not stored)
int tax(int amount) {
return amount * 8 / 100;
}
}
class Order {
final Customer customer; // association: a reference to an object that lives on its own
final List<LineItem> items = new ArrayList<>(); // composition: created and owned here
Payment payment;
Order(Customer customer) {
this.customer = customer;
}
void addItem(String sku, int price, int qty) {
items.add(new LineItem(sku, price, qty));
}
int total(TaxCalculator tax) { // dependency: passed in for this call only
int subtotal = items.stream().mapToInt(i -> i.price() * i.qty()).sum();
return subtotal + tax.tax(subtotal);
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order(new Customer("Ana"));
order.addItem("keyboard", 5000, 1);
order.addItem("cable", 500, 2);
order.payment = new CardPayment("4242");
System.out.println(order.customer.name() + ": " + order.items.size() + " line items, total " + order.total(new TaxCalculator())
+ ", paid by " + order.payment.describe());
}
}Output (the same in all three languages)
Ana: 2 line items, total 6480, paid by card ending 4242
5. In the interview
- Draw the class diagram first, roughly: boxes and lines in two minutes. Refine as you talk.
- Mark interfaces clearly («interface» above the name): they're the points where your design flexes.
- Draw one sequence diagram for the main flow. If an arrow has nowhere sensible to go, a class or method is missing.
- Don't fuss over notation. Interviewers care that composition vs association is right, not about the exact arrowhead.
6. Remember it
How this hook works: what “The map shows what is; the directions show one trip” means
What the slogan means, phrase by phrase
- “The map shows what is”
- Class diagram: classes, their parts and relationships, true at all times.
- “the directions show one trip”
- Sequence diagram: who calls whom, in what order, for one scenario.
Why this picture fits: A city map and turn-by-turn directions describe the same place, for different questions.
The story → the concept
| The city | The design |
|---|---|
| Streets and buildings | Classes and relationships |
| Owned vs nearby buildings | Composition vs association |
| Turn-by-turn directions | A sequence diagram |
| Not every street | Only what the discussion needs |
Watch it on an example
- Map: Order ◆ LineItem, Order → Customer, CardPayment ▷ Payment.
- Directions: Customer → Order.addItem → Order.total → TaxCalculator.tax → Payment.describe.
- A missing arrow (who computes tax?) shows a missing dependency.
The trap it saves you from
Spending ten minutes on perfect UML while the design stays wrong.
It still works when…
The interviewer prefers code. A two-minute class sketch still prevents most wrong turns.
7. Common mistakes
8. Practice exercise (25 minutes)
- Draw a class diagram (10 min). For the library problem (P09), from memory.
- Draw a sequence (10 min). Checkout of a book, including the waitlist check.
- Compare (5 min). Open P09 and compare. What did you miss?
9. Self-check quiz
An order's line items are deleted with the order. Which relationship?
A method receives a TaxCalculator as a parameter and doesn't keep it. Which relationship?
What does a sequence diagram show?
Which part of a class diagram matters most in an interview?
10. Go deeper
| Resource | Time | Why |
|---|---|---|
| Class diagram (Wikipedia) | Reference | The full notation, including the relationship arrows. |
| Diagramming and documenting (Software architecture) | 30 min | C4 and other views one level up. |
| Module 06: Concurrency | Next | The follow-up every LLD interview asks. |