UML Relationships
Beginner · 12 min read · ▶ live playground · ✦ checkpoint
A book-club diagram has six lines leaving one class. Someone calls all of them “has-a,” but one object is owned, one is shared, one is only borrowed for a method call, and one is not a part at all. The lines look close enough to invite a wrong design claim.
UML relationships describe more than “these types interact.” They can tell you whether one object knows another, groups it, owns it, or uses it briefly.
- An association is a stable knows-a relationship: one object keeps a reference to another independent object.
- An aggregation is a whole-part has-a relationship with a shared lifetime: the part can be shared or outlive the whole.
- Composition is an owns-a relationship with a coincident lifetime: the part belongs to the whole and has no separate role in this model.
- A dependency is a transient uses-a relationship: one object needs another for an operation but does not keep it as lasting state.
Read the relationship before the class details
The diagram uses one book club to put the four object relationships side by side. It also brings back extends and implements, which connect types rather than describing object ownership.
class diagram
A book club knows, shares, owns, and uses
- 1Association is knows-a: BookClub keeps a reference to an independent Facilitator.
- 2Aggregation is shared has-a: one ReadingGuide can outlive and serve several clubs.
- 3Composition is owns-a: the filled diamond marks the club's MembershipRoster part.
- 4Dependency is uses-a: BookClub needs AgendaPrinter only while printAgenda() runs.
- 5Implements reconnects a class to a contract; it does not describe object lifetime.
- 6Extends reconnects a child to its parent; it is an is-a type relationship.
There is an intentional notation limit in that picture. This diagram engine has one has edge, and it draws the filled diamond used for composition. It has no separate aggregation edge. The BookClub → ReadingGuide line therefore stays a plain association; the shared lifetime in the model is what lets you refine it to aggregation.
Do not read a hollow aggregation diamond into the picture—it is not there. The lesson is in the lifetime distinction, not in pretending the renderer drew a symbol it does not support.
Association: keep an independent object in view
BookClub keeps a reference to its Facilitator. The club knows who opens its sessions, but it does not own that person's existence. The facilitator can be created before the club, moved to another club, or remain after this club closes.
That is association. In code, it often appears as a field:
interface SessionHost {
openSession(): string;
}
class Facilitator implements SessionHost {
private name: string;
public constructor(name: string) {
this.name = name;
}
public openSession(): string {
return `${this.name} opens the session`;
}
public nameTag(): string {
return this.name;
}
}
class GuestFacilitator extends Facilitator {
public override openSession(): string {
return `${this.nameTag()} opens a guest session`;
}
}
class ReadingGuide {
private title: string;
public constructor(title: string) {
this.title = title;
}
public heading(): string {
return this.title;
}
}
class MembershipRoster {
private members: string[] = [];
public add(name: string): void {
this.members.push(name);
}
public count(): number {
return this.members.length;
}
}
class AgendaPrinter {
public print(text: string): string {
return `AGENDA: ${text}`;
}
}
class BookClub {
private facilitator: Facilitator;
private guide: ReadingGuide;
private roster = new MembershipRoster();
public constructor(
facilitator: Facilitator,
guide: ReadingGuide,
) {
this.facilitator = facilitator;
this.guide = guide;
}
public addMember(name: string): void {
this.roster.add(name);
}
public openSession(): string {
return `${this.facilitator.openSession()}: ${this.guide.heading()}`;
}
public printAgenda(printer: AgendaPrinter): string {
const text = `${this.guide.heading()} | ${this.roster.count()} members`;
return printer.print(text);
}
}
const sharedGuide = new ReadingGuide("Short Stories");
const mondayClub = new BookClub(
new Facilitator("Mina"),
sharedGuide,
);
const thursdayClub = new BookClub(
new GuestFacilitator("Noah"),
sharedGuide,
);
mondayClub.addMember("Omar");
mondayClub.addMember("Leena");
console.log(mondayClub.openSession());
console.log(mondayClub.printAgenda(new AgendaPrinter()));
console.log(thursdayClub.openSession());Mina opens the session: Short Stories AGENDA: Short Stories | 2 members Noah opens a guest session: Short Stories
The facilitator field is the association from the diagram. Assigning it in the constructor makes the connection stable for the club, but the facilitator remains an independent object.
Aggregation: share a part without owning its lifetime
The guide field is also assigned through the constructor, yet the problem gives it a stronger whole-part meaning: a reading guide is part of what a club follows. The same sharedGuide is passed to both clubs, and it can remain available if either club disappears.
That makes the relationship aggregation. A field declaration alone cannot prove it. Association and aggregation can have identical TypeScript syntax; the domain's whole-part meaning and lifetime decide which word fits.
Aggregation is useful when the “part” belongs to a broader pool or may serve several wholes. A player belongs to a team roster but still exists after leaving that team. A course groups students, but closing the course should not erase the students.
Composition: own the part and its lifetime
A field initializer is an expression that sets a field's starting value where the field is declared. BookClub uses one to create its own MembershipRoster. No caller supplies or shares that roster, and it is meaningful only as this club's membership record.
A coincident lifetime means the part begins and ends with the whole as a design concept. If this BookClub is discarded, its roster is discarded with it; moving that same roster to another club would break the ownership claim.
The filled diamond sits at BookClub, the whole. Its line connects to MembershipRoster, the owned part. This is the diagram's has relationship.
TypeScript uses garbage collection, automatic reclaiming of objects that running code can no longer reach, so your class does not manually destroy the roster. The UML meaning is still useful: no other object should depend on that roster continuing independently of its club.
Dependency: borrow a capability for one operation
printAgenda() receives an AgendaPrinter parameter, calls it, and does not save it in a field. The club depends on the printer to finish that operation, but the relationship ends with the call.
Method parameters, local variables, and short-lived calls often reveal dependencies. The dashed open arrow says “uses,” not “owns.” If BookClub began storing the printer for every future agenda, the relationship would become a lasting association instead.
Extends and implements answer a different question
The four relationships above connect objects and say something about knowledge, parts, or use. extends and implements connect types.
GuestFacilitator extends Facilitatoris an is-a relationship. Its solid hollow triangle points to the parent.Facilitator implements SessionHostsays the class fulfills a contract. Its dashed hollow triangle points to the interface.
Neither arrow says that one runtime object owns the other. A GuestFacilitator object does not contain a separate Facilitator object just because its class extends Facilitator.
When not to annotate every arrow
In an interview, relationship notation should make a design decision visible. Draw the filled diamond when ownership and lifetime matter. Draw a dependency when temporary use explains why a class does not store a helper. Use a plain association when “these objects stay connected” is all the discussion needs.
Do not add nodes for every string, date, logger, or one-line helper. A diagram with twenty perfectly classified arrows can hide the three relationships the interviewer needs to question. If lifetime rules are unknown, label the plain association and state the open question instead of inventing ownership.
You also do not need to argue over aggregation versus association when sharing has no consequence in the problem. Explain the lifetime in words, choose the smallest honest notation your tool supports, and move on to the behavior that affects the design.
Checkpoint
Answer all three to mark this lesson complete
You can now defend each line with a sentence about type, knowledge, use, or lifetime. Next, you will choose between two ways to define a shared type: an abstract class with common machinery and an interface with only a contract.