Lifetime, not decoration
Association, aggregation, composition
Object relationships describe how instances connect: composition, aggregation, or a simple reference. Example: a lot has floors, a floor has spots, a ticket refers to a spot, and a vehicle is tied to a ticket while parked. If you draw every line as an arrow and call it “has-a,” you will not know who is allowed to delete what.
Association
is a relationship without . Ticket → SpotId. Either side lives alone. Closing a ticket does not pour concrete back into the earth.
Aggregation
is whole-part where parts can outlive the whole, or be shared. A department “has” employees who existed before the reorg. In LLD rounds, say the lifetime out loud and move on.
Composition
is stronger. The part dies with the whole. Floors belong to one lot. Spots belong to one floor. addFloor and addSpot go through the owner.
The only question that matters
If I remove the parent, does the child still make sense? If no, you are looking at composition. If yes, you are looking at association — maybe with a collection on one side. UML hollow versus filled diamonds are a footnote. Interviewers grade the API: who constructs, who holds, who deletes.
Ownership drives APIs. ParkingLot.addFloor(...) is composition. Ticket holding a SpotId is association. Get that wrong and exit() starts destroying concrete. The ticket’s job is to close a stay. The floor’s job is to keep the rectangle.
How to explain this in an interview
Composition ParkingLot ◆── Floor ◆── ParkingSpot Association Ticket ──> SpotId Association Ticket ──> Vehicle Association Ticket ──> TicketRepository (store, not child)
The lot owns floors. The ticket only names a spot.
class ParkingLot {
private final List<Floor> floors = new ArrayList<>();
void addFloor(Floor floor) {
floors.add(Objects.requireNonNull(floor));
}
void closeLot() {
floors.clear(); // composition: floors go with the lot
}
}
class Ticket {
private final TicketId id;
private final SpotId spotId;
void close() {
// closes the stay. Does not delete the parking spot.
}
}| Pair | Kind | If parent is removed |
|---|---|---|
| Lot → Floor | Composition | Floors are gone. They had no other home. |
| Floor → Spot | Composition | Spots are gone. Concrete was the floor’s. |
| Ticket → SpotId | Association | Spot remains. The stay is history. |
| Ticket → Vehicle | Association | The car still exists in the world. |
| Show → Seat (movie) | Composition | Those seats were that hall’s layout. |
| Booking → SeatId | Association | The seat is free again, not deleted. |
| User → Expense (Splitwise) | Association | The dinner can still be settled. |
| Board → Cell (game) | Composition | Cells are the board’s representation. |
Every arrow is composition
“Has-a” is not ownership. A ticket has a spot id. The spot outlives the ticket. Draw association, or you will delete the wrong thing when close() runs.
Child deletes the parent’s parts
Ticket.close() must not remove the ParkingSpot from the floor. Release occupancy. Leave the rectangle where it is. That is lifecycle in one sentence.
Many-to-many is still association
A course has many students. A student has many courses. Neither owns the other. You may introduce an Enrollment entity if the relationship itself has data — grade, term, waitlist. That enrollment is an entity with identity. The course and the student remain associated through it. Do not make Student a child of Course just because the UI shows a roster.
The same pattern appears in Splitwise groups and in movie bookings with many seats. If the link has a life — reserved, paid, cancelled — model the link. If it is only a pointer, keep an id. Read collections ownership when the “has many” is a list that must not leak.
Composition is not inheritance
People hear “is made of” and reach for extends. A lot is not a kind of floor. A floor is not a kind of spot. Those are parts. Variation in behavior — how we allocate, how we price — is also composition: the lot holds a strategy. That story lives in composition versus inheritance. Here the word composition means lifetime of parts, not “prefer has-a for reuse.”
How to draw it without wasting the round
Draw the physical tree with filled diamonds or a nested list. Draw tickets and vehicles with open arrows and ids. Say “the lot creates floors; the ticket only names a spot.” If the interviewer cares about UML vocabulary, you already have the words. If they do not, you still have a design. Either way you did not spend ten minutes coloring diamonds.
Then implement the owner’s API. addFloor, findFree, occupy, release. Do not hand out the live list. That is how relationship choices become code instead of a picture that lies.
Movie hall: seats compose, bookings associate
A Show owns a SeatMap. Those seats are the hall’s layout for that screening. A Booking holds SeatIds. When the booking cancels, the seats are free — not deleted. Candidates who compose Booking out of Seat objects start destroying chairs.
class Show {
private final ShowId id;
private final SeatMap seats; // composition: this screening's layout
Optional<Seat> findFree(SeatId id) {
return seats.find(id).filter(Seat::isFree);
}
}
class Booking {
private final BookingId id;
private final ShowId showId;
private final List<SeatId> seatIds; // association: names, not chairs
void cancel() {
// frees reservations. Does not remove seats from the hall.
}
}Bidirectional pointers are a smell, not a kind
Spot holding Ticket and Ticket holding Spot is two owners. You will close one and forget the other. Prefer one direction plus an id the other way, or a lookup on the owner. “I need to go both ways” is usually “I need a query on the floor.” That query is cheaper than a cycle that delete() cannot unwind.
Elevator cars and requests
A building composes cars. A car composes a door and a state. A HallRequest is associated with a floor and later with a car id. When the request completes, the car is still there. If you let Request.close() destroy a Car, you have modeled the wrong diamond. Say the lifetime. Then the API writes itself.
Shared parts — a printer used by many departments, a pricing strategy used by many lots in a test — are association or aggregation. Do not compose a shared strategy into one lot and then wonder why the second lot’s destructor nulled it. Shared things are not children.
The interview sentence
“If I remove the parent, does the child still make sense? Floors: no. Tickets naming spots: yes.” That is the whole UML lecture. Then implement addFloor on the lot and close on the ticket without touching concrete.
Library copies versus titles
A Title is a catalog idea. A Copy is a physical book the library owns. Loan associates a Member with a CopyId. If you compose Loan out of Copy, returning a book deletes the copy. If you treat Title as a copy, two members check out the same ISBN and you have one row. Lifetime again: the library composes copies. Loans only name them.
Members are not composed by the library in the UML sense that they die when the branch closes — they can move. That is association, even if the UI says “our members.” Do not let the marketing sentence pick the diamond.
Draw lifetime arrows, not every has-a
You do not need a complete UML set. You need two kinds of lines: dies-with, and names. Label them. Then implement only the dies-with side as add/remove on the owner. The names side is an id field. That is enough relationship design for a machine-coding round.