If you remember five things
All three are "has-a." They differ only in how tight the grip is. Everything below defends these five calls.
- Lifecycle decides it. Ask one question: if I delete the whole, should the part die with it? That answer, not vibes, picks the relationship.
- Composition owns. The part cannot exist without the whole and belongs to exactly one whole. Filled diamond. The whole creates it with `new` and destroys it.
- Aggregation shares. The part has its own identity, outlives the whole, and can be held by many. Hollow diamond. You inject it, you do not build it.
- Association just talks. Two objects collaborate, neither owns the other. Plain arrow. Most of your dependency-injected wiring lands here.
- Inheritance is a different axis. `is-a`, not `has-a`. Treating it as one of these three is the classic blunder (looking at you, `Stack extends Vector`).
1The problem it solves#
The second you have two classes, you owe an answer: how do they relate? "Connected" is lazy, and no interviewer lets it slide.
Picture a parking lot:
- A
ParkingLot, a few thousandParkingSpotobjects, aTicketper car that drives in. - They are clearly connected. The interviewer wants the exact kind of connection, because it pins down three concrete things.
The three questions a relationship answers:
- Lifecycle. Tear down the lot and the spots vanish too (a spot means nothing without its lot). A car leaving does not delete the lot.
- Ownership. Who creates and destroys the other object? Exactly one class holds that job, or nobody does and you leak.
- Sharing. Can one object be pointed at by many, or does it belong to exactly one owner?
Association, aggregation, and composition are the three answers. Get them right and your diagram tells the truth about the system. Get them wrong and you ship code where deleting a cart cascades a delete into the user who owned it. I have watched that exact bug reach production because the ORM mapping blindly copied a bad class diagram.
These three relationships are just three answers to "when the whole dies, what happens to the part?" Composition says it dies too, aggregation says it lives on and can be shared, association says they were never really parent and child. Reason from lifecycle and the UML takes care of itself.
2The idea (mental model)#
All three are "has-a." They only differ in how tight the grip is. Here is how I keep them straight at the whiteboard.
- Association (loosest). Two objects know each other and talk, neither owns the other. A
Doctortreats aPatient: both existed before they met, both keep living after. It is a phone number in a contact list. Deleting the number does not delete the person. - Aggregation (shared part). A "has-a" where the part can live on its own and be shared. A
TeamhasPlayerobjects. The team folds, the players still exist and join another team tomorrow. The team holds references to players it did not create and will not destroy. A playlist and its songs: delete the playlist, the songs stay, and ten playlists can hold the same song. - Composition (owned part). A "has-a" where the part cannot exist without the whole and belongs to exactly one whole. A
HousehasRoomobjects. No house, no rooms. You do not move a room to another house. The whole creates the parts and destroys them. This is ownership in the strong sense, a cascade delete in database terms.
The one-line test I use in interviews:
If I delete the container, should the contained thing die with it? Yes, and it belongs to only one container, that is composition. No, it can live on and be shared, that is aggregation. They just talk and neither owns the other, that is plain association.
And one thing that is not on this axis at all:
- Inheritance (
is-a) is a different question entirely. - I will draw it for contrast, but it is not one of the three, and treating it like one is a top-three mistake I cover below.
3Structure#
A small e-commerce slice shows all three cleanly, and everyone already has a model of a shopping cart.
Orderis made ofOrderItemobjects. The items have no meaning outside their order. Kill the order, kill the items. Composition (filled diamond).Orderreferences aCustomer. The customer exists before the order and long after it ships. The order does not own the customer, and many orders point at the same one. Aggregation (hollow diamond), shared.Orderuses aPaymentGatewayto charge. It calls it once and forgets it. Plain association.PremiumOrderis anOrder. Inheritance, shown only for contrast.
OrderItem sits on the owner side: "I made this and I will destroy it." The hollow diamond on Customer says "I hold a reference, I did not make it and I will not end it." The plain arrow to PaymentGateway says "I use this for a moment." The open triangle on PremiumOrder is is-a. Four symbols, and that is the entire vocabulary you need at a whiteboard.4Implementation in Java#
The relationship type shows up directly in how objects get constructed and stored. Watch where new lives, because new is where ownership is decided.
// COMPOSITION: Order creates and owns its items.
// OrderItem is package-private; nothing outside this file builds one.
public class Order {
private final String id;
private final Customer customer; // aggregation: passed in, not created here
private final List<OrderItem> items = new ArrayList<>();
public Order(String id, Customer customer) {
this.id = id;
this.customer = customer; // we hold a reference, we did not build it
}
public void addItem(String productId, int qty, Money unitPrice) {
// The item is born here, inside the order. Pure composition.
items.add(new OrderItem(productId, qty, unitPrice));
}
public Money total() {
return items.stream()
.map(OrderItem::subtotal)
.reduce(Money.ZERO, Money::plus);
}
// Defensive copy: we own the items, so we never hand out the live list.
public List<OrderItem> items() {
return List.copyOf(items);
}
// ASSOCIATION: we use a gateway we are handed, just to do one job.
public boolean checkout(PaymentGateway gateway) {
return gateway.charge(total());
}
}
// The part. It is meaningless without an Order around it.
class OrderItem {
private final String productId;
private final int quantity;
private final Money unitPrice;
OrderItem(String productId, int quantity, Money unitPrice) {
this.productId = productId;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
Money subtotal() {
return unitPrice.times(quantity);
}
}
Three signals you can point at in the code, and I will ask you to point at them:
- Composition:
new OrderItem(...)happens insideOrder. The items live in a field no one else constructs. When theOrderis garbage collected with no live reference left, the items go with it. - Aggregation:
Customerarrives through the constructor. TheOrderstores the reference but never callsnew Customer(...). Someone outside owns the customer's life, usually a repository or a session. - Association:
PaymentGatewaycomes in as a method parameter, not even a field. The order touches it for one call and does not retain it.
Reveal the answer
The field-versus-parameter tell:
- A standing field usually means "I keep a lasting relationship with this."
- A method parameter usually means "I borrow it for a second."
- When I grade, I literally scan for where the dependency is stored. It is a strong hint, but lifecycle is the real judge (see the self-check above).
Here is the checkout flow so you can see the borrowed object in action.
Order. It arrives as a call argument, does one job, and is gone when checkout returns. That is association in motion: a borrowed collaborator, not an owned part.5A real-world example#
A music app, because almost everyone has used one, so the intuition is free.
PlaylistaggregatesSong. You add songs you do not own. Delete the playlist and the songs stay in the catalog for everyone. Two playlists hold the same song by reference. Shared, independent lifecycle. Textbook aggregation.Songis composed ofAudioSegmentchunks (or metadata blocks) that exist only to make up that one song. Delete the song and those chunks are meaningless and go away. One owner, dependent lifecycle.Playerassociates with anAudioOutputDevice. It sends audio to whatever speaker is connected right now. It does not own your Bluetooth headphones, and unplugging them does not destroy the player.
Why I reach for this one in real interviews:
- It forces the candidate to defend "shared and independent" versus "owned and dependent" with something they have physical intuition about.
- If someone tells me a playlist composes its songs, I ask: so when I delete my road-trip playlist, your copy of that song vanishes from your library? Watch them reconsider in real time. That moment is the point.
The same shape repeats everywhere:
- A
UniversityaggregatesStudentobjects. - A
Documentcomposes itsParagraphobjects. - A
RideRequestassociates with aDriver. - Once you see the pattern you stop memorizing definitions and start reasoning from lifecycle.
6When to use it, and when not to#
Composition
- Use when
- The part is an implementation detail with no identity outside the whole.
- Examples
- Line items, rooms in a house, the engine in one specific car, cells in a spreadsheet.
- Tell
- You call `new` inside the owner, and nobody outside should ever hold a reference to the part.
- Breaks when
- You over-compose: the part cannot be reused or unit-tested in isolation because it cannot even be constructed alone.
Aggregation
- Use when
- The part has its own identity and lifecycle and may be shared.
- Examples
- Players on teams, students in courses, products stocked in a warehouse.
- Tell
- You construct the part somewhere else and inject it in.
- Breaks when
- You actually needed one strict owner and sharing lets the part drift out of sync or outlive its purpose.
Reach for association when two objects collaborate but neither is a part of the other: services calling services, a controller using a repository, an order charging a gateway. Most of your dependency-injected wiring is association, and that is completely fine.
When not to force it:
- Do not model a relationship as composition because it feels "stronger" or more object-oriented. Over-composing creates rigid designs you cannot reuse or test in isolation.
- Do not add a relationship at all if the two classes do not genuinely need each other. The best relationship is sometimes no relationship, and a flat design with fewer edges is easier to reason about than a clever one.
7Common mistakes#
Confusing aggregation and composition. The big one, and what most borderline candidates get wrong.
- The test is lifecycle, not vibes.
- Ask: can the part outlive the whole? Can it be shared by two wholes at once?
- Yes to either means aggregation.
Treating inheritance as a has-a. Stack extends Vector from early Java is the classic blunder.
- A stack is not a kind of vector. It uses a list internally and should hide most of it. That should be composition, not inheritance.
- When in doubt, prefer composition over inheritance: more flexible, it does not leak the parent's whole public API, and it lets you swap the backing implementation later.
Bidirectional references everywhere.
- If
Orderpoints toCustomerandCustomerholds aList<Order>, you now have two sides to keep in sync, plus a cycle that bites you in serialization and object equality. - Make relationships one-directional unless both sides genuinely need to navigate, and even then question it.
Mistaking a field for ownership.
- Holding a reference is not owning. A
Loggerfield is association, not composition, even though it lives in a field. - Who created it and who destroys it decides the relationship, not where you stashed the pointer.
Leaking composed parts.
- If your
Orderreturns its internalList<OrderItem>by reference, callers mutate it behind your back and your "ownership" is a fiction. - Return a copy or an unmodifiable view, which is exactly why the
items()method above usesList.copyOf.
Drawing a filled diamond because composition "feels stronger," then folding the moment I ask "so deleting this whole deletes that part everywhere, even the copy another object is using?" If you cannot defend the diamond with one sentence about lifecycle and sharing, you did not choose it, you guessed it. Guessing on the arrows is exactly what separates the borderline candidate from the hire.
I am not grading UML purity. I am grading whether your diagram and your code tell the same story. If you drew composition, I expect new inside the owner. If you drew aggregation, I expect injection through the constructor. When those two disagree, I trust neither, and I start wondering what else in your design is decoration rather than decision.
8Where it shows up in interviews#
Almost every LLD prompt lands here in the first ten minutes. Parking lot, elevator, vending machine, library management, ride-sharing: all of them are mostly an exercise in getting these three relationships right across a dozen classes. The patterns you layer on top come later and are easier.
What I am actually grading:
- Do you draw the correct diamond, and justify it in one sentence about lifecycle, not UML trivia?
- When I push ("why aggregation and not composition here?"), do you answer with ownership and sharing, or fold and flip?
- Does your code match your diagram? Composition wants
newinside the owner. Aggregation wants injection through the constructor.
What a strong candidate sounds like:
Candidate: The lot composes its spots, because a spot has no meaning without the lot, so tearing down the lot tears down the spots. But it aggregates tickets, since a ticket's data outlives the drive and we archive it for billing.
Interviewer: And if I delete a spot, what happens to the ticket that was parked there?
Candidate: Nothing destructive. The ticket references the spot id, it does not own the spot. That reference is an association, so the ticket survives and we just mark the spot gone.
Draw the diamond, name the lifecycle, defend it in one sentence, move on.
The trap to avoid:
- Do not spend five minutes philosophizing about UML purity.
- Interviewers care that your design is correct and defensible, not that you can recite the formal definition.
"Why is Order to OrderItem composition and not aggregation?"
"You store Customer in a field, just like items. Why is one aggregation and the other composition?"
"When would you turn one of these into inheritance instead?"
"Order has a Customer and Customer has a list of Orders. Any problem?"
"Your Order owns its items. How do you stop callers from mutating them?"
What you can now explain in the room
- Why lifecycle (not strength or vibes) is the one test that picks composition versus aggregation versus association.
- How where `new` lives decides ownership, and why a stored field is a hint rather than proof.
- Why `Order` composes its items, aggregates its customer, and associates with its gateway, each in one sentence.
- How to read the four UML symbols (filled diamond, hollow diamond, plain arrow, open triangle) and draw the right one.
- Why inheritance is a separate axis and why `Stack extends Vector` should have been composition.
- How to defend a composed part with a defensive copy so ownership is real, not decorative.
Next, look at the LLD classics that lean on these relationships: Parking Lot, Library Management, and the Composite and Strategy patterns, where "prefer composition over inheritance" stops being advice and becomes the whole point.
Get a prep plan built around your Class Relationships: Association, Aggregation, Composition interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.