Back to the path
Level 1 intermediate 23 min #uml#design#notation#relationships#interview

UML Relationships & Notation

The exact UML notation interviewers expect on the whiteboard — association, aggregation, composition, inheritance, realization, dependency — and how each line and arrowhead maps to real code and multiplicities.

Why the arrowheads actually matter

In an LLD interview you will draw classes on a whiteboard, and the lines between them carry as much information as the boxes. A hollow diamond versus a filled diamond is the difference between “the order references its customer” and “the order owns its line items and they die with it.” Interviewers read these instantly. Drawing the wrong arrowhead signals you don’t understand the lifecycle and ownership of your own objects.

This lesson gives you the six relationships of a UML class diagram, the exact glyph for each, the multiplicity annotations, and — crucially — the code each one maps to. By the end you should be able to look at any two classes and pick the correct line without hesitation.

The class diagram is the single most-used artifact in LLD interviews. You don’t need to memorize all 14 UML diagram types — you need to draw class diagrams fluently and correctly.

Tip

This six-way taxonomy (association, aggregation, composition, generalization, realization, dependency) is the most common enumeration, but it isn’t the only one. Some texts fold aggregation and composition under “association” as mere adornments, and others treat realization as a kind of dependency. If a pedantic interviewer pushes back, say: “I’m using the common six-relationship breakdown; aggregation/composition are technically association subtypes.” That defuses the nitpick instantly.

The notation cheat sheet

Here is the full vocabulary. Read the table once, then we’ll take each row apart.

RelationshipGlyph (source → target)SemanticsCode signal
Associationsolid line ——— (often with open arrow ——▶ for navigability)“uses / knows about”, a structural linka stored field/reference to the other class
Aggregationsolid line + hollow diamond at owner ◇———”has-a”, shared, independent lifecyclereference held but not owned; often injected
Compositionsolid line + filled diamond at owner ◆———”owns-a”, exclusive, dependent lifecycleowner creates & destroys the part
Inheritance (generalization)solid line + hollow triangle at parent ——▷”is-a”, subclass extends superclassextends / base class
Realizationdashed line + hollow triangle at interface ┈┈▷”implements”, fulfills a contractimplements / pure virtual
Dependencydashed line + open arrow ┈┈▶”depends on” transientlyparameter, local var, return type, new in a method

Two axes explain the whole table:

  • Solid vs dashed line. Solid = a persistent structural relationship (the object holds onto the other as a field). Dashed = a transient relationship (it just touches it briefly) or a contract (realization).
  • The arrowhead shape encodes what kind of relationship: triangle = type hierarchy, diamond = whole/part ownership, open arrow = navigability / direction of “uses”.
Key idea

The diamond always sits on the side of the whole (the container/owner). The triangle always points to the more general type (the parent/interface). Get the side right and you’ve communicated ownership and direction correctly even if your line style is rough.

A note on the C++ tabs below: to keep the focus on the relationship being illustrated, the #include directives (<vector>, <string>, <memory>, <utility>) and any using are elided. The fragments are otherwise valid under -std=c++17; add the headers when you compile them standalone.

1. Association — “knows about”

An association is the plainest structural link: one class holds a reference to another as a field and can send it messages. No ownership is implied — just that an object of A is durably connected to an object of B across method calls.

+-----------+              +-----------+
|  Driver   |------------->|    Car    |
+-----------+              +-----------+
        a Driver knows about a Car

Two distinct UML concepts get conflated here, so be precise:

  • Presence of a line says the link exists.
  • Navigability (the open arrowhead) says which way you can traverse it at runtimeDriver ——▶ Car means a Driver holds and can reach its Car, but a Car does not hold a back-pointer to its Driver.

A plain undecorated solid line leaves navigability unspecified (in strict UML that often means “navigable both ways or unknown”). In an interview, prefer the directed arrow: it documents who holds a reference to whom, which is exactly the coupling information the interviewer wants.

class Car {}
class Driver {
constructor(private car: Car) {}   // stored field -> association
drive() { /* uses this.car */ }
}

Aggregation and composition are actually special, stronger kinds of association. When ownership is genuinely unclear and you can’t justify a diamond, a plain association arrow is rarely penalized — but note the boundary set in §6: if the reference is only touched inside a method and never stored as a field, then it is a dependency, not an association, and drawing a solid association line there is wrong.

2. Aggregation — “has-a”, shared

Aggregation is a whole/part relationship where the part can outlive the whole and may be shared by several wholes. The classic example: a Team has Players, but if the team disbands the players still exist (they can join another team).

+-----------+  1      0..*  +-----------+
|   Team    |<>------------>|  Player   |
+-----------+               +-----------+
   hollow diamond at the "whole" (Team)

The hollow diamond <> sits on Team. As a heuristic (not a definition), the part is often passed in from outside rather than created internally — but be careful: dependency injection can also pass a composed part in, so “passed in” alone does not prove aggregation. The real test is lifecycle and sharing.

class Player {}
class Team {
private players: Player[] = [];
add(p: Player) { this.players.push(p); }  // injected, shared
}
// players exist independently; deleting the Team doesn't delete them
Common pitfall

Aggregation vs. plain association is one of UML’s genuinely fuzziest distinctions, and many graders treat the two identically. Don’t burn interview time agonizing over which to draw. The practical rule: reserve the diamond for cases where you can say something about the whole/part lifecycle; if you can’t, draw a plain association arrow and move on. The line that really earns points is composition — so spend your precision budget there.

3. Composition — “owns-a”, exclusive lifecycle

Composition is the strong form: the part is exclusively owned by one whole and is destroyed when the whole is destroyed. A House is composed of Rooms — demolish the house and the rooms are gone; a room never belongs to two houses.

+-----------+  1      1..*  +-----------+
|   House   |<#>----------->|   Room    |
+-----------+               +-----------+
   FILLED diamond at the "whole" (House)

The filled diamond <#> sits on the owner. The owner typically creates the parts internally and never hands ownership away:

class Room {
constructor(public name: string) {}
}
class House {
private rooms: Room[];
constructor() {
  // House CREATES its rooms -> they live and die with the House
  this.rooms = [new Room("kitchen"), new Room("bath")];
}
}
Tip

The C++ tab makes the distinction physical: composition is a member by value (or std::unique_ptr), aggregation is a std::shared_ptr, association is a non-owning raw pointer/reference. In an interview say: “I’ll model Room as a value member of House so the rooms are destroyed with the house — that’s composition.”

Aggregation vs Composition — the one-line test

Ask: “If I delete the whole, should the part die too?”

  • Yes, and the part belongs to exactly one whole → composition (filled diamond).
  • No, the part lives on / can be shared → aggregation (hollow diamond).

Order ◆—— OrderLine (lines are meaningless without the order → composition). Order ◇—— Customer (customer outlives the order, has many orders → aggregation/association).

4. Inheritance / Generalization — “is-a”

A solid line with a hollow triangle pointing at the parent. SavingsAccount is a BankAccount. The triangle always points toward the more general type.

        +---------------+
        |  BankAccount  |   <-- triangle points UP to the parent
        +---------------+
                /_\         (hollow triangle, SOLID line)
                 |
        +-----------------+
        | SavingsAccount  |
        +-----------------+
class BankAccount {
withdraw(amount: number) { /* ... */ }
}
class SavingsAccount extends BankAccount {  // is-a
applyInterest() { /* ... */ }
}

5. Realization — implementing an interface

A dashed line with a hollow triangle pointing at the interface. This is “implements a contract” rather than “extends an implementation.” The dashed line distinguishes it from inheritance: there’s no inherited code, only an obligation to fulfill the methods.

        +----------------+
        | <<interface>>  |   <-- stereotype sits INSIDE the box
        |    Shape       |
        |   area()       |
        +----------------+
              /_\           (hollow triangle, DASHED line)
               :
               :  (dashed)
        +----------------+
        |    Circle      |
        |   area()       |
        +----------------+

UML marks interfaces with the <<interface>> stereotype (or a circle “lollipop”). Note the precise difference from §4: both inheritance and realization use a hollow triangle. The distinguishing feature is the linesolid line = generalization, dashed line = realization — not the triangle shape. Saying “filled triangle for realization” is a classic mis-statement; it is always hollow.

interface Shape { area(): number; }      // contract
class Circle implements Shape {           // realization
constructor(private r: number) {}
area() { return Math.PI * this.r * this.r; }
}

6. Dependency — “uses transiently”

The weakest link: a dashed line with an open arrow. Class A depends on B if A uses B without holding it as a structural field — B appears as a method parameter, return type, local variable, or is new-ed inside a method. Change B’s interface and A might break, but A doesn’t contain a B.

+-------------+         +-------------+
|  Report     |- - - - >|  PdfWriter  |
+-------------+         +-------------+
   uses a PdfWriter only inside a method (parameter)
class PdfWriter { write(s: string) {} }
class Report {
export(writer: PdfWriter) {     // parameter only -> dependency
  writer.write("...");
}                               // no field, nothing stored
}

Association vs Dependency — the distinction interviewers probe

  • If A stores a B as a field (it persists between method calls) → association (solid line).
  • If A only touches B inside a single methoddependency (dashed line).

This is the rule that makes the “never wrong to draw association” intuition false: substituting a solid association line where the reference is genuinely method-local misrepresents the coupling and is marked down on rubrics.

Multiplicities — how many on each end

Numbers near the line ends say how many objects participate. They go at the far end — the number near class B states how many B’s relate to one A.

NotationMeaning
1exactly one
0..1zero or one (optional)
* or 0..*zero or more (many)
1..*one or more (at least one)
2..5a specific range
+-----------+  1        0..*  +-------------+
|  Customer |---------------->|   Order     |
+-----------+                 +-------------+
 one Customer  <-->  zero-or-more Orders

+-----------+  1        1..*  +-------------+
|   Order   |<#>------------->| OrderLine   |
+-----------+                 +-------------+
 one Order   composes   one-or-more OrderLines

Read it as: “one Customer has zero-or-more Orders; each Order belongs to exactly one Customer.” The 1..* on OrderLine says an order must have at least one line — that’s a real business invariant you’ve now encoded in the diagram.

Watch out

Don’t leave multiplicities off in an interview. Order ——▶ OrderLine is vague; Order 1 ——▶ 1..* OrderLine tells the interviewer you’ve thought about whether an empty order is legal. Missing or wrong multiplicities are the most common silent point-loss on whiteboard diagrams.

Putting it together: a worked diagram

A small e-commerce slice exercising every relationship at once:

            +----------------+
            | <<interface>>  |
            | PaymentMethod  |
            |   pay()        |
            +----------------+
                  /_\
                   :  (realization, dashed)
            +----------------+
            |  CreditCard    |
            +----------------+

+-----------+  1     0..* +-----------+  1      1..*  +-----------+
| Customer  |◇----------->|   Order   |◆------------->| OrderLine |
+-----------+(aggregation)+-----------+ (composition) +-----------+
                                |
                                | - - - - - - - >  +-----------+
                                |  (dependency)    |  Invoice  |
                                v                  +-----------+
                         uses an Invoice only inside checkout()

How to narrate this in an interview:

  1. Customer ◇—— Order (aggregation, 1 to 0..*): a customer has many orders, but deleting a customer record shouldn’t necessarily vaporize order history → hollow diamond. (If pressed, I’d accept a plain association arrow here too — the load-bearing claim is that it is not composition.)
  2. Order ◆—— OrderLine (composition, 1 to 1..*): lines are meaningless without their order and an order needs at least one → filled diamond, 1..*. This is the line worth getting exactly right.
  3. CreditCard ┈▷ PaymentMethod (realization): CreditCard fulfills the payment contract → dashed line, hollow triangle pointing at the interface.
  4. Order ┈▶ Invoice (dependency): checkout() builds an Invoice locally and returns it; Order doesn’t keep a field referencing it. Because the reference never survives the method call, this is a transient use → dashed line with an open arrow, not a solid association. If instead Order stored its Invoice as a member, this would become an association (solid line) — that single distinction (stored field vs method-local) is exactly what separates the two.

Narrating why each glyph was chosen — lifecycle for the diamonds, contract-vs-code for the triangles, stored-field-vs-method-local for solid-vs-dashed — is what separates a candidate who memorized symbols from one who understands the model.

Recap

  • Solid line = persistent structural link (stored field). Dashed line = transient use or a contract.
  • Diamond sits on the whole: hollow = aggregation (part outlives/shared), filled = composition (part dies with the whole, exclusive).
  • Hollow triangle points at the general type: solid line = inheritance (extends code), dashed line = realization (implements a contract). The triangle is always hollow; the line distinguishes the two.
  • Open arrow = navigability (association) or transient dependency (dashed).
  • Always annotate multiplicities. Reserve filled diamonds for true exclusive ownership. When aggregation vs association is genuinely unclear, don’t agonize — pick one, state your lifecycle reasoning, and move on.

Assessment

Pass mark: 70% on the concept questions unlocks the next lesson.

1. On a UML class diagram you see a line ending in a FILLED diamond at class `Document` and a plain end at class `Paragraph`, with multiplicities `1` near Document and `1..*` near Paragraph. What does this assert?

2. Class `OrderService` has a method `notify(channel: NotificationChannel)` and never stores `channel` as a field. Which line correctly models the relationship from `OrderService` to `NotificationChannel`?

3. Which statements about UML inheritance (generalization) and realization are TRUE? Select all that apply. (select all)

4. Your interviewer points at a `Team` and a `Player` and asks why you drew a HOLLOW diamond instead of a filled one. Which justifications correctly support aggregation over composition? Select all that apply. (select all)

Design problem 5

Model a ride-sharing domain as a UML class diagram. Include at minimum: Rider, Driver, Trip, Vehicle, and PaymentMethod (with concrete CreditCard and Wallet implementations). For EVERY relationship, state the line type (association / aggregation / composition / inheritance / realization / dependency), the side the arrowhead/diamond/triangle sits on, and the multiplicities. Justify each choice in terms of ownership and lifecycle, and explicitly call out one relationship where aggregation-vs-association is genuinely ambiguous and how you'd defend your call.