Aggregation — Has-A with Independent Lifecycle
Introduction
A university Department has professors. The Computer Science department has Prof. Smith, Prof. Jones, and Prof. Lee. But if the CS department is restructured and merged with Electrical Engineering, those professors don't cease to exist — they continue working, perhaps in the new combined department.
This is Aggregation: a "has-a" relationship where the whole contains parts, but the parts can exist independently of the whole. The department has professors, but it doesn't own their existence.
In the previous tutorial, we learned that Association is any relationship where objects know about each other. Aggregation is a specialized form of association that adds one extra dimension: whole-part semantics. One object is the container (the whole), and the other objects are its members (the parts).
The critical distinction from Composition (next tutorial): in Aggregation, the parts survive if the whole is destroyed. A professor outlives a department restructuring. A player outlives a team dissolution.
What Makes Aggregation Different from Association?
Both Association and Aggregation involve objects holding references to each other. The difference is semantic — it's about intent and meaning, not about code syntax:
Association says: "I know about you." There's no whole-part hierarchy. A Professor is associated with a Course — neither is the "container" of the other.
Aggregation says: "I contain you as a part, but I don't control your existence." A Department has Professors — the department is the whole, professors are the parts. But professors exist independent of any department.
Think of it through these real-world examples:
- A library has books → Aggregation. If the library closes, the books still exist (they're moved or sold).
- A team has players → Aggregation. If the team disbands, the players still exist and can join other teams.
- A parking lot has cars → Aggregation. If the parking lot is demolished, the cars are driven away — they don't vanish.
- A shopping cart has products → Aggregation. If you abandon the cart, the products return to the shelves.
The lifecycle test: If the whole is destroyed, do the parts survive? If yes → Aggregation. If no → Composition (next tutorial).
UML Notation
Visualization

Code Example
Continuing our university domain from the Association tutorial: a Department aggregates Professor objects. Professors are created independently and passed into the department — they aren't created by the department. When the department is dissolved, the professors still exist.
from typing import List, Optional
class Professor:
"""Professor exists independently of any department."""
def __init__(self, employee_id: str, name: str, specialization: str):
self._employee_id = employee_id
self._name = name
self._specialization = specialization
@property
def name(self) -> str:
return self._name
@property
def specialization(self) -> str:
return self._specialization
def __repr__(self) -> str:
return f"Prof. {self._name} ({self._specialization})"
class Department:
"""
Department AGGREGATES professors.
Key: professors are created OUTSIDE and passed IN.
If the department is dissolved, professors still exist.
"""
def __init__(self, name: str, budget: float):
self._name = name
self._budget = budget
self._professors: List[Professor] = [] # Aggregation: holds references, not ownership
def add_professor(self, professor: Professor) -> None:
"""Add an externally-created professor to this department."""
if professor not in self._professors:
self._professors.append(professor)
print(f" {professor.name} joined {self._name}")
def remove_professor(self, professor: Professor) -> None:
"""Remove professor from department — professor continues to exist."""
if professor in self._professors:
self._professors.remove(professor)
print(f" {professor.name} left {self._name}")
def get_headcount(self) -> int:
return len(self._professors)
def dissolve(self) -> List[Professor]:
"""Dissolve the department — return professors (they survive!)."""
surviving_profs = list(self._professors)
self._professors.clear()
print(f" {self._name} dissolved. {len(surviving_profs)} professors reassigned.")
return surviving_profs # Professors survive — independent lifecycle
def __repr__(self) -> str:
prof_names = [p.name for p in self._professors]
return f"{self._name} Department ({len(self._professors)} profs: {prof_names})"
# Demonstration: lifecycle independence
print("--- Creating professors INDEPENDENTLY ---")
smith = Professor("P001", "Smith", "Algorithms")
jones = Professor("P002", "Jones", "Machine Learning")
lee = Professor("P003", "Lee", "Databases")
print("\n--- Creating department and adding professors ---")
cs_dept = Department("Computer Science", 500_000)
cs_dept.add_professor(smith)
cs_dept.add_professor(jones)
cs_dept.add_professor(lee)
print(f" {cs_dept}")
print("\n--- Department dissolved — professors survive ---")
freed_professors = cs_dept.dissolve()
print(f" Department after dissolution: {cs_dept}")
print(f" Professors still exist: {freed_professors}")
print("\n--- Professors join a new department ---")
ece_dept = Department("Electrical & Computer Engineering", 600_000)
for prof in freed_professors:
ece_dept.add_professor(prof)
print(f" {ece_dept}")import java.util.ArrayList;
import java.util.List;
class Professor {
private final String employeeId;
private final String name;
private final String specialization;
public Professor(String employeeId, String name, String specialization) {
this.employeeId = employeeId;
this.name = name;
this.specialization = specialization;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Prof. " + name + " (" + specialization + ")";
}
}
class Department {
private final String name;
private final double budget;
private final List<Professor> professors = new ArrayList<>(); // Aggregation
public Department(String name, double budget) {
this.name = name;
this.budget = budget;
}
public void addProfessor(Professor professor) {
if (!professors.contains(professor)) {
professors.add(professor);
System.out.println(" " + professor.getName() + " joined " + name);
}
}
public void removeProfessor(Professor professor) {
if (professors.remove(professor)) {
System.out.println(" " + professor.getName() + " left " + name);
}
}
public int getHeadcount() {
return professors.size();
}
// Dissolve department — professors survive (independent lifecycle)
public List<Professor> dissolve() {
List<Professor> survivingProfs = new ArrayList<>(professors);
professors.clear();
System.out.println(" " + name + " dissolved. " + survivingProfs.size() + " professors reassigned.");
return survivingProfs;
}
@Override
public String toString() {
List<String> names = professors.stream().map(Professor::getName).toList();
return name + " Department (" + professors.size() + " profs: " + names + ")";
}
}
public class Main {
public static void main(String[] args) {
System.out.println("--- Creating professors INDEPENDENTLY ---");
Professor smith = new Professor("P001", "Smith", "Algorithms");
Professor jones = new Professor("P002", "Jones", "Machine Learning");
Professor lee = new Professor("P003", "Lee", "Databases");
System.out.println("\n--- Creating department and adding professors ---");
Department csDept = new Department("Computer Science", 500_000);
csDept.addProfessor(smith);
csDept.addProfessor(jones);
csDept.addProfessor(lee);
System.out.println(" " + csDept);
System.out.println("\n--- Department dissolved — professors survive ---");
List<Professor> freedProfessors = csDept.dissolve();
System.out.println(" Department after dissolution: " + csDept);
System.out.println(" Professors still exist: " + freedProfessors);
System.out.println("\n--- Professors join a new department ---");
Department eceDept = new Department("Electrical & Computer Engineering", 600_000);
for (Professor prof : freedProfessors) {
eceDept.addProfessor(prof);
}
System.out.println(" " + eceDept);
}
}The Aggregation Litmus Test
When deciding if a relationship is Aggregation, apply these three tests:
Test 1: The "Survives Destruction" Test
If the container (whole) is destroyed, do the parts continue to exist?
- Department dissolved → professors survive? Yes → Aggregation ✅
- Order cancelled → order items exist independently? No → Composition ❌
Test 2: The "Created Outside" Test
Are the parts created independently and passed into the whole?
- Professors exist before joining a department → Aggregation ✅
- OrderItems only exist because an Order created them → Composition ❌
Test 3: The "Shared Membership" Test
Can a part belong to multiple wholes simultaneously?
- A professor can be in a department AND a research group → Aggregation ✅
- A room can only belong to one building → Composition (exclusive ownership) ❌
If all three tests point to Aggregation, use the open diamond ◇. If any points to dependent lifecycle, consider Composition (next tutorial).
Common Mistakes
Mistake 1: Using Aggregation When Composition is Correct
The problem: Modeling Order ◇→ OrderItem as aggregation. But an OrderItem has no meaning without its parent Order — it wasn't created independently, and destroying the Order should destroy its items.
The fix: If the part's lifecycle is bound to the whole → use Composition (◆), not Aggregation (◇).
Mistake 2: Aggregation vs Association Confusion
The problem: Every "has" relationship is modeled as aggregation. "A person has a phone number" — is that aggregation?
The fix: Aggregation implies whole-part semantics with a clear container. A person having a phone number is more accurately modeled as an attribute or simple association. Ask: "Is this a container-member relationship?" If not, use plain association.
Mistake 3: Overusing Aggregation in UML Diagrams
The problem: Adding open diamonds everywhere in class diagrams, making every collection look like aggregation.
The fix: Martin Fowler's practical advice: if you're not sure whether something is aggregation or plain association, use plain association. The diamond adds meaning only when the whole-part semantics are genuinely important to the design. Don't decorate for decoration's sake.
Key Takeaways
- Aggregation is a specialized association with whole-part semantics — one object contains others as members
- The defining characteristic: parts have independent lifecycle — they survive if the whole is destroyed
- Parts are typically created outside the whole and passed in (injected, added)
- Parts can be shared across multiple wholes simultaneously
- UML notation: open (hollow) diamond ◇ on the whole side of the relationship
- The litmus test: "If I destroy the container, do the parts survive?" Yes → Aggregation
- In the next tutorial, we'll see Composition — where the answer is "No, the parts are destroyed too" — and the diamond becomes filled ◆
- You'll use Aggregation extensively in domain modeling (S8) and design problems like Parking Lot (lots aggregate parking spots) and Library (libraries aggregate books)