The idea
What it is
Once you have classes, the interesting part of design is how objects relate to each other. Three relationships come up constantly, and beginners mix them up all the time: association, aggregation, and composition. They all describe one object connected to another — the difference is entirely about ownership and lifecycle.
Here's the intuition. Association is a plain link — a Driver knows about a Car, but neither owns the other and they live and die on their own schedule. Aggregation is a has-a where the parts are shared and can outlive the whole — a Team has Players, but disband the team and the players still exist. Composition is a stronger owns-a where the parts are created and destroyed with the whole — a House owns its Rooms, and demolish the house and the rooms go with it.
The one question that decides everything
Ask: what happens to the part when the whole is destroyed? If the part survives untouched, it's association or aggregation. If the part is destroyed along with the whole, it's composition. That single test settles almost every case.
Mechanics
How it works
Association — a plain link (uses-a / knows-a)
The loosest relationship. One object holds a reference to another so it can use it, but there's no ownership in either direction. A Driver is associated with a Car — the driver can call car.drive(), but the car was made elsewhere and will keep existing whether or not this driver does. Their lifecycles are completely independent.
- The object is usually passed in from outside — you didn't create it, you just got a reference to it.
- Destroying one side leaves the other untouched.
- In UML, association is a plain solid line:
Driver ———— Car.
Aggregation — a shared whole-part (has-a)
A has-a relationship where one object groups others, but the parts are shared and can outlive the whole. A Team has Players — but the players exist independently. Disband the team and every player still exists; they might join another team tomorrow. The team is just a container that references parts it does not own.
- Parts are added from outside —
team.add(player)— not built by the team itself. - The same part can belong to several wholes at once (a player on a club and a national squad).
- In UML, aggregation is a hollow diamond on the whole's end:
Team ◇———— Player.
Composition — an owned whole-part (owns-a)
The strongest owns-a relationship. The whole creates its parts and is solely responsible for destroying them. A House owns its Rooms — the rooms are built inside the house's constructor and they only exist as part of that house. Demolish the house and the rooms are gone with it. The classic engineering example is a Car and its Engine: the car builds the engine, and the engine has no life of its own once the car is scrapped.
class Engine {
constructor(public hp: number) {}
}
class Car {
private engine: Engine; // exclusive part
constructor(hp: number) {
this.engine = new Engine(hp); // CREATED inside the owner → composition
}
// when the Car is gone, nothing references this.engine → it dies too
}Contrast that with aggregation, where the part arrives from outside and is merely held: team.add(existingPlayer). The keyword to look for is who created the part and who is responsible for ending its life. If the owner did both, it's composition. If the part came from elsewhere, it's aggregation or association.
Aggregation and composition are both "has-a"
Don't get hung up on telling aggregation and composition apart by the words has-a vs owns-a — they overlap. The reliable difference is lifecycle: in aggregation the part can outlive the whole (shared, independent); in composition the part is created and destroyed with the whole (exclusive, owned). Association is the odd one out — it's a peer link, not a whole-part at all.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
Three relationships side by side — Association (Driver → Car), Aggregation (Team ◇ Players), and Composition (House ◆ Rooms). Press Destroy the whole on each and watch what happens to the parts: the car lives on, the players escape to a free pool, and the rooms die with the house.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Association — the car survives
In the Association scenario, press Destroy the whole. The Driver box vanishes but the Car stays fully intact — the console logs "Car still exists — independent lifecycle". Nothing owned anything; cutting the link harms neither side.
Aggregation — the players escape
In the Aggregation scenario, press Destroy the whole. The Team disappears, but its Player chips survive and slide down into the free pool below — the console logs "Players survive the team". Shared parts outlive the whole that grouped them.
Composition — the rooms die too
In the Composition scenario, press Destroy the whole. The House and every Room inside it fade out together — the console logs "Rooms destroyed with the house". Exclusive parts share the owner's fate. Hit Reset to rebuild all three and compare the outcomes again.
In practice
When to use it — and what trips people up
How to choose — follow the lifecycle
Pick the relationship by answering one question: if the whole is destroyed, should the part go with it? If yes, model composition — let the owner create the part in its constructor and clean it up. If the part should keep living on its own, you want aggregation — accept the part from outside and just hold a reference. If there's no whole-part feeling at all and the two objects are peers that merely use each other, it's a plain association.
A second tell is who creates the part. If the owner builds it, that's a strong signal for composition. If the part is handed in from the caller (and might be shared with others), that's aggregation or association. When in doubt, prefer the looser relationship — it's far easier to tighten an aggregation into a composition later than to untangle an over-eager composition that's destroying objects other code still needs.
Composition is a promise to destroy
Choosing composition means you are now responsible for the part's whole life — including ending it. If anything outside might still need that part, composition is the wrong call: you'll either delete data others depend on, or fight to keep it alive. Reach for composition only when the part truly cannot exist without the whole.
What it gives you
- Modeling the right relationship makes ownership obvious — readers instantly know who creates and destroys each object.
- It prevents lifecycle bugs: composition cleans up its parts automatically; aggregation lets shared parts survive correctly.
- It clarifies your UML and your API — a hollow vs filled diamond tells the next engineer exactly how the parts are managed.
- Choosing aggregation where parts are shared avoids duplicating objects and keeps a single source of truth.
Common mistakes
- Using composition where aggregation belonged — the owner deletes a part that other code still references, causing accidental destruction or dangling references.
- Leaking ownership — handing out a reference to an owned part lets outside code mutate or hold it past the owner's lifetime.
- Treating a plain association as ownership — destroying a peer object that was never yours to destroy.
- Lifecycle bugs from the reverse mistake: aggregating parts that should have been owned, so nobody ever cleans them up and they leak.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// ASSOCIATION — Driver just holds a reference passed in from outside.
class Car {
constructor(public plate: string) {}
}
class Driver {
constructor(private car: Car) {} // car created elsewhere; not owned
drive() { return `driving ${this.car.plate}`; }
}
// AGGREGATION — Team collects shared Players added from outside.
class Player {
constructor(public name: string) {}
}
class Team {
private players: Player[] = [];
add(p: Player) { this.players.push(p); } // shared; outlives the team
}
// COMPOSITION — House builds its Rooms inside its own constructor.
class Room {
constructor(public name: string) {}
}
class House {
private rooms: Room[];
constructor() {
this.rooms = [new Room("kitchen"), new Room("bedroom")]; // owned & exclusive
}
// when the House is gone, its rooms are unreachable → destroyed with it
}References & further reading
4 sources- Articleen.wikipedia.org
Wikipedia — Object composition
Covers composition and aggregation as whole-part relationships, with the lifecycle distinction front and center.
- Articleen.wikipedia.org
Wikipedia — Class diagram
The UML reference for the notation — plain line, hollow diamond, and filled diamond — used to draw these relationships.
- Articleen.wikipedia.org
Wikipedia — Association (object-oriented programming)
Defines association as a peer link and frames aggregation and composition as its specialized whole-part forms.
- Book
UML Distilled — Martin Fowler
A concise, practical guide to UML that explains when a hollow vs filled diamond actually earns its keep in a design.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Nothing is scored or saved.
question 01 / 04
A Team holds a list of Player objects that were created elsewhere and can join other teams. Disband the team and the players still exist. Which relationship is this?
question 02 / 04
What is the single most reliable test to tell composition apart from aggregation?
question 03 / 04
In UML, which symbol on the whole's end of the line denotes composition (the part is created and destroyed with the whole)?
question 04 / 04
A Driver is constructed with a Car that was created by other code. The driver only calls car.drive(). Destroying the driver should leave the car untouched. What is this?
0/4 answered