All chapters in this track
- 01Chapter 1: Why Object Orientation Exists
- 02Chapter 2: Classes, Objects, and Instances, Precisely Defined
- 03Chapter 3: The Four Pillars I: Encapsulation
- 04Chapter 4: The Four Pillars II: Abstraction
- 05Chapter 5: The Four Pillars III: Inheritance
- 06Chapter 6: The Four Pillars IV: Polymorphism
- 07Chapter 7: Composition vs. Inheritance
- 08Chapter 8: Interfaces and Abstract Classes: When to Use Which
- 09Chapter 9: SOLID, Part 1: Single Responsibility & Open/Closed
- 10Chapter 10: SOLID, Part 2: Liskov Substitution, Interface Segregation, Dependency Inversion
- 11Chapter 11: Basic UML for Communicating Design
- 12Chapter 12: Anti-Patterns: God Objects, Tight Coupling, Deep Hierarchies, the Fragile Base Class, Anemic Domain Models
- 13Chapter 13: OOP Idioms Across TypeScript and Python — and Where OOP Is the Wrong Tool
- 14Chapter 14: When OOP Breaks Down: Real-World Failure Modes
- 15Chapter 15: Design Exercise: Parking Lot System
- 16Chapter 16: Design Exercise: Library Management System
- 17Chapter 17: Design Exercise: Elevator Controller
- 18Chapter 18: OOP in Interviews: What a Strong Answer Looks Like
Chapter 11 of 18
Chapter 11: Basic UML for Communicating Design
Why this matters
Design discussions fall apart when people use the same words to mean different relationships — one engineer's "has-a" is composition, another's is aggregation, and neither notices until a bug shows up around object lifetime. UML class diagrams give a precise, widely-recognized vocabulary for exactly this: what's inherited, what's held, and — critically — who owns what and for how long. You don't need the full UML specification; you need the handful of notations that show up constantly in design docs, whiteboards, and interviews.
The core concept
[BEGINNER] A UML class box has three sections: class name, fields (with types), and methods (with signatures and visibility markers + public, - private, # protected).
+-----------------------------+
| Account |
+-----------------------------+
| - balanceCents: number |
| + id: string |
+-----------------------------+
| + deposit(amount: number) |
| + withdraw(amount: number) |
| + getBalanceCents(): number |
+-----------------------------+
Relationships, precisely
[SENIOR INSIGHT] This is where UML earns its keep — the three relationships that look similar in casual conversation but mean very different things for object lifetime and coupling:
| Relationship | Arrow | Meaning | Lifetime | Code shape |
|---|---|---|---|---|
| Inheritance ("is-a") | ▷——— (hollow triangle, solid line) | Subclass extends superclass | N/A — a type relationship | class Savings extends Account |
| Composition ("owns-a", part-of) | ◆——— (filled diamond, solid line) | The whole owns the part; the part cannot exist independently | Part is destroyed when the whole is | An Order and its OrderLine items — line items have no meaning without the order |
| Aggregation ("has-a", loosely) | ◇——— (hollow diamond, solid line) | The whole has the part, but the part can exist independently | Part outlives the whole | A Team and its Players — a player still exists if the team is disbanded |
| Association (uses/references) | ——— (plain line) | One class refers to or uses another, no ownership implied | Independent | A Driver associated with a Car they're currently driving |
The distinction between composition and aggregation is the one people conflate most, and it's exactly the "has-a" concept from Chapter 7 needing one more level of precision: does the contained object's lifetime depend on the container?
Order ◆──── OrderLine (composition: line items die with the order)
Team ◇──── Player (aggregation: players survive if the team disbands)
Driver ──── Car (association: no ownership either way)
In code, this distinction usually shows up as: composition means the container creates and destroys the contained objects internally (they're never passed in from outside, never shared with another owner); aggregation means the contained objects are created independently and merely referenced.
// Composition — OrderLine instances are created and owned entirely by Order.
class OrderLine {
constructor(public sku: string, public quantity: number) {}
}
class Order {
private lines: OrderLine[] = [];
addItem(sku: string, quantity: number): void {
this.lines.push(new OrderLine(sku, quantity)); // created internally, owned exclusively
}
}
// Aggregation — Player instances exist independently and are merely referenced.
class Player {
constructor(public name: string) {}
}
class Team {
private players: Player[] = [];
addPlayer(player: Player): void {
this.players.push(player); // passed in from outside — Team doesn't own its lifetime
}
}
class OrderLine:
def __init__(self, sku: str, quantity: int) -> None:
self.sku = sku
self.quantity = quantity
class Order:
def __init__(self) -> None:
self._lines: list[OrderLine] = []
def add_item(self, sku: str, quantity: int) -> None:
self._lines.append(OrderLine(sku, quantity)) # composition: created internally
class Team:
def __init__(self) -> None:
self._players: list["Player"] = []
def add_player(self, player: "Player") -> None:
self._players.append(player) # aggregation: passed in, independently owned
A worked example diagram
[IN PRACTICE] Here's how these notations combine to describe a small system — a library (the domain used again in Chapter 16's full design exercise) — precisely enough that a reader could redraw it without seeing the original image:
+----------------+ +----------------+
| Library | ◆────── | LibraryBranch | (composition: a branch belongs to exactly
+----------------+ 1 * +----------------+ one Library and is destroyed with it)
|
| ◇ 1 *
v
+----------------+
| Book | (aggregation: a book exists independently
+----------------+ of any one branch — it can transfer)
^
| ▷ (inheritance)
+-----------+-----------+
| |
+----------------+ +----------------+
| PhysicalBook | | EBook |
+----------------+ +----------------+
+----------------+ +----------------+
| Member | ———──── | Loan | (association: a member is linked to their
+----------------+ +----------------+ active loans, no ownership implied)
Multiplicities (1, *) on either end of a relationship line specify how many of each side can participate — Library 1 ◆── * LibraryBranch reads as "one Library owns zero-or-more Branches."
Common mistakes
- Using composition and aggregation interchangeably in a design doc. This isn't pedantry — it directly affects how deletion, serialization, and testing should behave. Deleting a
Libraryshould cascade-delete itsLibraryBranchrecords (composition); it must never cascade-deleteBookrecords that other branches still reference (aggregation). - Drawing every relationship as a plain association because it's the easiest to draw, losing the lifetime information that actually mattered for the design decision at hand.
- Treating a UML diagram as a deliverable in itself rather than a communication tool. A diagram nobody checks against the actual code drifts out of date and becomes actively misleading — worse than no diagram.
- Skipping multiplicities. "A Team has Players" is ambiguous about whether a team can have zero players, exactly one, or unlimited — the multiplicity markers remove that ambiguity.
Checklist: using UML effectively
- Use inheritance arrows only for genuine is-a, substitutable relationships — same substitutability test as Chapters 5–6, just drawn instead of coded.
- Distinguish composition from aggregation deliberately by asking "does the contained object's lifetime depend on the container?" — this single question resolves the ambiguity every time.
- Include multiplicities on association ends when they matter to the design decision being communicated (e.g., "can a Book have zero branches" is a real question with a real answer).
- Keep diagrams at the level of the current discussion — a high-level design conversation doesn't need every private field shown; a detailed LLD review might.
- Treat a diagram as disposable documentation of a decision at a point in time, not a contract that must be kept eternally in sync with code — redraw it when it's needed for the next discussion, rather than maintaining it as living documentation unless the team has committed to that.
Discussion / interview questions
-
"What's the difference between composition and aggregation, and why does the distinction matter in practice?" Strong answer: Composition: the contained object's lifetime is owned by the container (destroyed together); aggregation: the contained object exists independently. Matters for deletion cascades, serialization boundaries, and understanding what "owns" a piece of data.
-
"Draw (or describe) a class diagram for an Order containing OrderLines, where OrderLines cannot exist without their Order." Strong answer: Uses a filled-diamond composition relationship from
OrdertoOrderLine, correctly reasoning thatOrderLines are created and destroyed byOrder. -
"How would you represent inheritance versus interface implementation differently in UML?" Strong answer: Both use a hollow-triangle arrow toward the parent/interface; interface implementation is conventionally drawn with a dashed line (
- - -▷) while class inheritance uses a solid line (——▷), and the target is labeled<<interface>>for an interface. -
"Why might a team decide not to keep UML diagrams continuously up to date with the codebase?" Strong answer: Diagrams are a communication tool for a specific design discussion, not always a maintained artifact; an out-of-date diagram that nobody checks against the code can actively mislead, so many teams treat them as disposable, generated fresh when needed rather than committed to staying in sync.
-
"What does a multiplicity of
1to*mean on a relationship between Library and LibraryBranch?" Strong answer: One Library can have zero or more LibraryBranches (reading the*end), while each LibraryBranch belongs to exactly one Library (reading the1end) — correctly reads both ends of the relationship, not just one.
Chapter summary / cheat sheet
- A UML class box: name, fields (with visibility markers), methods.
- Inheritance (
▷, hollow triangle): is-a, substitutable. Composition (◆, filled diamond): owns-a, dependent lifetime. Aggregation (◇, hollow diamond): has-a, independent lifetime. Association (plain line): uses/references, no ownership. - The composition-vs-aggregation question that resolves ambiguity: "does the contained object's lifetime depend on the container?"
- Multiplicities (
1,*, etc.) on relationship ends remove ambiguity about how many participants are allowed on each side. - Treat diagrams as communication tools for a specific discussion, not necessarily as artifacts requiring permanent synchronization with the code.