Appearance
9.2.7 — Class Relationships and UML Class Diagrams
In an LLD interview you will be asked to design a system on a whiteboard, and within ten minutes you will be drawing boxes with lines between them. The lines are not decoration. Each kind of line means something specific about who owns what and who dies with whom, and interviewers grade the difference. This page covers the six relationships, the notation for each, and what each one looks like in TypeScript.
The running example is the delivery app: an Order has lines, a Customer has an address, a Restaurant has a menu, and an OrderService uses a payment gateway.
1. The class box itself
Before the lines, the box. A UML class box has three compartments:
The visibility markers are worth knowing because interviewers use them to check that you thought about encapsulation: + is public, - is private, # is protected, and a static member is underlined. An abstract class name is written in italics, and so is an abstract method.
In a real interview, draw only what carries your point. A diagram of twelve classes with every field listed is unreadable and burns your time. Draw names and relationships first, then add the two or three methods that matter to the design decision you are explaining.
2. The six relationships
Here is the complete notation on one figure. Everything after it is detail.
Take them one at a time, each with the code it corresponds to.
Association — "knows about"
A lasting link where one object holds a reference to another, and neither owns the other's lifetime.
typescript
class Order {
constructor(readonly customer: Customer) {} // holds a reference; does not own it
}Drawn as a plain solid line. Deleting the order does not delete the customer, and the customer existed before this order and will exist after it.
Multiplicity goes at the ends and says how many participate: 1, 0..1, 1..* (one or more), 0..* (any number), or an exact range like 2..5. Reading the association above: one order is placed by exactly one customer. If you also draw the reverse — one customer has zero or more orders — you have a two-way association, which in code means both classes hold references to each other. Avoid that when you can, because two-way links have to be kept in sync and it is easy to update one side and not the other.
Aggregation — "has, but does not own"
A whole-part relationship where the parts can exist independently and can be shared.
typescript
class DeliveryTeam {
private couriers: Courier[] = []; // has couriers
add(c: Courier): void { this.couriers.push(c); }
}Drawn with a hollow diamond at the whole's end. Disband the team and the couriers still exist, still employed, possibly already on another team. The same courier object can belong to two teams at once.
Composition — "owns; the parts die with it"
A whole-part relationship where the parts belong to exactly one whole and cannot outlive it.
typescript
class Order {
private readonly lines: OrderLine[];
constructor(lines: OrderLineInput[]) {
this.lines = lines.map((i) => new OrderLine(i)); // (1) creates its own parts
}
addLine(input: OrderLineInput): void {
this.lines.push(new OrderLine(input)); // (2) parts never come from outside
}
}Drawn with a filled diamond. Line (1) is the tell: the whole creates its parts rather than receiving them. Line (2) keeps it that way, so no OrderLine ever exists outside an order. Delete the order and its lines are meaningless, so they go too — which in a database is exactly a cascading delete, and in memory is simply the lines becoming unreachable when the order does.
The distinction that interviewers actually probe: aggregation versus composition is a question about lifetime and exclusivity, not about how the code stores it. Both are usually a field holding an array. Ask two questions: can this part exist on its own, and can it belong to two wholes at once? Two noes mean composition.
Worked examples: Order and OrderLine is composition, since a line for two burgers means nothing outside its order. Playlist and Song is aggregation, since the song exists in the library and appears on many playlists. House and Room is composition. Company and Employee is aggregation, because an employee outlives the department they sit in.
Composition matters in code beyond the drawing: it tells you the whole is the aggregate root from 9.2.2 section 7, so outside code should hold the Order and never an OrderLine, and every change to a line goes through a method on the order.
Dependency — "uses temporarily"
The weakest relationship: one class mentions another in a parameter, a local variable, or a return type, but does not keep it.
typescript
class OrderService {
place(order: Order, clock: Clock): Receipt { // uses Clock, does not store it
const now = clock.now();
/* … */
}
}Drawn as a dashed line with an open arrow. This is what you draw when a class only knows about another for the duration of a call. It is the loosest connection you can have while still having one, and if you can turn an association into a dependency by passing something in rather than storing it, the design usually gets simpler.
Generalization — "is a kind of"
Inheritance. Drawn as a solid line with a hollow triangle pointing at the parent.
typescript
class CardPayment extends Payment { /* … */ }The triangle always points at the more general thing. Everything from 9.2.4 applies to whether you should be drawing this at all.
Realization — "plays the role of"
A class implementing an interface. Drawn as a dashed line with a hollow triangle pointing at the interface, which is labelled with «interface» above its name.
typescript
class BikeCourier implements Deliverable { /* … */ }Same triangle as generalization because both mean "can be used as", and dashed because there is no inherited code — only a contract. That single visual difference, solid versus dashed, is exactly the distinction the previous page spent a section on.
3. Which line to draw, as a decision procedure
In an interview you will hesitate over this, so compress it into four questions:
- Does A inherit from B? Solid line, hollow triangle. Does A implement an interface B? Dashed line, hollow triangle.
- Does A only mention B inside a method? Dashed line, open arrow. Done.
- Does A store B as a field? Then it is a solid line, and one more question decides which kind.
- Can B exist without A, or belong to two A's? Yes to either means a hollow diamond (aggregation) or a plain line (association). No to both means a filled diamond (composition).
And one piece of practical advice for the whiteboard: if you are unsure between association and aggregation, draw the plain line. Nobody will fault you for it, and the meaningful distinction — the one interviewers care about — is composition versus everything else, because that is the one with real consequences for deletion, for transaction boundaries and for who is allowed to hold a reference.
4. A worked diagram
Here is the delivery app's core, drawn with everything above.
Read it back in words, because that is exactly what you will do out loud in an interview:
CustomertoOrderis a plain association with1and0..*. One customer places any number of orders, and each order belongs to one customer. Not composition, because deleting a customer must not silently delete their order history — that is a legal and accounting question, and the diagram is where you should raise it.OrdertoOrderLineis composition, filled diamond,1..*. Lines are created by the order, belong to it alone, and are meaningless without it. This is also the statement thatOrderis the aggregate root, so nothing outside holds anOrderLinedirectly.OrderLinetoMenuItemis a plain association,1. The line refers to the menu item it was ordered from, and that item exists in the restaurant's menu independently and is referenced by thousands of other lines. Note the design consequence this drawing forces you to think about: the line must copy the price at order time rather than reading it from the menu item, because the restaurant will change prices tomorrow and last week's order must not change with it.OrderServicetoOrderis an association, since the service works with orders.OrderServicetoPaymentMethodis dashed, a dependency, because in this design the payment method is passed into the method rather than stored.CardPaymentandCodPaymenttoPaymentMethodare realizations: dashed lines, hollow triangles, pointing at the interface.
Notice how much design the diagram forced into the open: the price-copying decision, the "do not cascade a customer delete" decision, and the fact that nothing outside the order touches its lines. That is the actual value of drawing it, and it is why the exercise is worth doing even alone at a desk.
5. What interviewers are checking, and the mistakes that cost marks
They are checking that your relationships are honest. Drawing composition between Playlist and Song says songs are deleted when a playlist is, which is wrong and which a good interviewer will ask about. Being able to say "aggregation here, because the song is in the library and appears on many playlists" is a complete answer in ten words.
They are checking multiplicity. Writing 1 where the answer is 0..* hides a requirement. Most of the interesting follow-up questions live at the multiplicity: what if an order has zero lines, what if a customer has a thousand, what if two drivers claim the same delivery.
The four mistakes that come up most:
Drawing every relationship as an arrow with no shape. It is not wrong, exactly, but it says nothing, and you have skipped the part being graded.
Two-way associations everywhere. If Order holds a Customer and Customer holds an Order[], both sides must be kept in sync, and the object graph becomes impossible to load or serialise without care. Prefer one direction — usually the child pointing at the parent — and get the other direction from a query.
Turning every noun into a class. A diagram with thirty boxes is a vocabulary list, not a design. The classes worth drawing are the ones with behaviour or with a rule to protect.
Confusing generalization with realization. Solid triangle line for extends, dashed triangle line for implements. It is a one-second fix and it signals that you know the difference between inheriting code and promising a contract.
6. Where diagrams help and where they do not
Class diagrams are good at exactly one thing: showing structure at a glance — who holds whom, who is a kind of what, and what is optional. That is genuinely valuable in a whiteboard interview, in a design document, and when explaining an unfamiliar module to a new joiner.
They are bad at showing behaviour over time. A class diagram cannot tell you the order things happen in, what happens when a payment fails halfway, or how a request flows through six objects. For that you need the other diagram types, and 9.7.2 covers them in the LLD context: sequence diagrams for the order of calls between objects, state machine diagrams for an object's lifecycle such as an order moving from placed to preparing to delivered, and activity diagrams for a workflow with branches.
Two habits keep diagrams useful rather than a chore. Draw the diagram that answers a question, not the diagram that documents everything — a five-box figure explaining one decision beats a forty-box figure explaining nothing. And do not maintain diagrams by hand for code that changes weekly, because a stale diagram is worse than none: it is confidently wrong. Either generate them from the code, or keep them at the level of things that change slowly, which is usually the module boundaries rather than the classes.
What the next page adds. The relationships here are static shapes. 9.2.8 is about the design choice underneath them: when to make a class have something instead of be something, and the three techniques — composition, delegation and mixins — for sharing behaviour without inheritance.
Recall
- A UML class box has three compartments: name (italic if abstract), attributes, operations. Visibility markers:
+public,-private,#protected, underline for static. In an interview, draw only what carries your point. - The six relationships: association (solid line, stores a reference), aggregation (hollow diamond, has but does not own), composition (filled diamond, owns and parts die with it), dependency (dashed line, uses only inside a method), generalization (solid line + hollow triangle,
extends), realization (dashed line + hollow triangle,implements). - Two shapes carry nearly all the meaning: solid means a stored field, dashed means a passing use; a triangle means "can be used as".
- Aggregation versus composition is a question about lifetime and exclusivity, not storage: can the part exist alone, and can it belong to two wholes? Two noes mean composition, which also names the aggregate root that outside code must go through.
- Multiplicity at the line ends (
1,0..1,1..*,0..*) is where the interesting requirements hide. Getting it wrong hides a requirement. - Mistakes that cost marks: shapeless arrows, two-way associations everywhere, a box per noun, and mixing up solid
extendswith dashedimplements. - Class diagrams show structure, never behaviour over time. Sequence, state and activity diagrams cover that (9.7.2). Draw the diagram that answers one question, and do not hand-maintain diagrams of fast-changing code.
Self-test: Give the notation for all six relationships from memory. What two questions separate aggregation from composition, and what does the answer change in the code? Why is Order to OrderLine composition but OrderLine to MenuItem not? What design decision does that second relationship force you to make about price? Name the four common diagram mistakes.
Quiz Bank
FoundationalExplain aggregation versus composition, with the notation and an example of each.
Both are whole-part relationships, and the difference is lifetime and exclusivity.
Aggregation means the whole has the parts but does not own them. The parts exist independently, can be shared between several wholes, and survive the whole's deletion. Drawn as a solid line with a hollow diamond at the whole's end. Example: a Playlist and its Song objects. Delete the playlist and every song is still in the library, and each song appears on many playlists at once.
Composition means the whole owns the parts. Parts belong to exactly one whole, are usually created by it, and are meaningless without it. Drawn with a filled diamond. Example: an Order and its OrderLine objects. A line saying "two burgers" has no meaning outside its order, no other order shares it, and deleting the order should delete the lines.
The two questions that decide it: can the part exist on its own, and can it belong to two wholes at the same time? Two noes mean composition.
What changes in the code, which is why the distinction matters beyond the drawing. Composition means the whole creates its own parts rather than receiving them, which is what stops anyone outside from holding a reference and mutating a part behind the whole's back. It means the whole is the aggregate root, so every change to a part goes through a method on the whole. And it means deletion cascades — in a database that is an actual ON DELETE CASCADE, and in memory it is the parts becoming unreachable when the whole does.
FoundationalWhat is the difference between generalization and realization, in notation and in meaning?
Generalization is inheritance, drawn as a solid line with a hollow triangle pointing at the parent. It means the child is a kind of the parent, and it inherits the parent's code as well as its contract.
Realization is implementing an interface, drawn as a dashed line with a hollow triangle pointing at the interface, which is labelled «interface». It means the class promises to provide the interface's operations, and it inherits no code at all.
Why the same triangle: both mean "an instance of this can be used wherever that is expected", which is substitutability, and that is what the triangle marks.
Why one is dashed: dashed lines throughout UML mean a weaker or non-code-carrying relationship. Realization carries no implementation, only a promise.
Why the distinction matters in a design conversation, not just on paper: a solid triangle tells the reader you took on the fragile base class risk from 9.2.4, because your class now depends on the parent's internals as well as its interface. A dashed triangle says you took on only a contract. When an interviewer sees you draw the dashed one and say "I only need the contract here", that is the signal they are looking for.
AppliedYou are asked to diagram a library system: Library, Book, BookCopy, Member, Loan. Which relationship goes where, and what does each choice force in the code?
Library to BookCopy is composition, filled diamond, 1..*. A physical copy belongs to exactly one library and is meaningless without it, so if the library closes its copies do not migrate anywhere. In code the library creates copies, and nobody outside holds one without going through the library.
Book to BookCopy is aggregation or plain association, 1 to 0..*. Book here is the title — the ISBN, the author, the description — while BookCopy is a physical object on a shelf with a barcode. The book exists in the catalogue whether or not any copy does, so it is not composition. Separating the two is the design decision the diagram forces, and it is the one interviewers are actually probing: without it you cannot answer "how many copies of this title are out on loan", and you cannot represent a title the library has stopped stocking.
Loan to BookCopy and Loan to Member are both plain associations, 1 each. A loan links exactly one copy to exactly one member. Neither is owned by the loan, and both outlive it.
Member to Loan is an association, 1 to 0..*. The tempting alternative, composition, is wrong and importantly so: when a member leaves, their loan history must survive for accounting and for the record of what happened, so it must not cascade.
What each choice forces in the code. The composition on copies means library.addCopy(book) rather than new BookCopy(...) anywhere else. The book-versus-copy split means availability is a query over copies, not a boolean on the book. And the non-cascading member relationship means the delete path for a member is a soft delete or an anonymisation rather than a hard one, which is a real requirement you surfaced by choosing a line shape rather than by waiting for the question.
InterviewIn an LLD interview you draw ten classes with plain arrows between them and no multiplicities. What have you lost?
You have skipped the part being graded. Anyone can list the nouns in a problem statement. What the interviewer is testing is whether you understand the consequences of the connections, and every one of those consequences lives in the notation you left out.
Lost with the diamonds: who owns whom. Without them the diagram cannot say whether deleting an order deletes its lines, whether two teams can share a courier, or which object is the aggregate root that outside code must go through. Those are exactly the questions that come up in the follow-up round about transactions and deletes.
Lost with multiplicity: the requirements. 1 versus 0..* is the difference between "an order has a customer" and "an order might have no customer yet", which decides whether the field is nullable, whether the database column allows null, and whether guest checkout is supported at all. Most good follow-up questions live here: what if there are zero, what if there are ten thousand, what if two of them are the same.
Lost with the solid-versus-dashed distinction: whether you know the difference between inheriting code and promising a contract, and whether a relationship is a stored field or a passing parameter. That distinction is the entire content of 9.2.6 compressed into one line style.
What to do instead, in the time you actually have. Draw fewer boxes and annotate them properly. Six classes with diamonds and multiplicities demonstrates more than fifteen boxes joined by identical arrows, and it leaves you time to talk about the two decisions that matter. And say the relationships out loud as you draw — "composition here, because a line cannot exist outside its order" — because the reasoning is what is being scored, and it also catches your own mistakes before the interviewer does.