The Confusion That Cost Me a Job Interview
"Explain the difference between aggregation and composition," the interviewer asked. I froze. I knew they were both "has-a" relationships, but couldn't articulate the difference. I lost the job.
Three years later, I finally get it. And I'll explain it through a story you'll never forget: a university system. No abstract diagrams, no confusing theory—just a clear story with runnable Python code.
By the end, you'll know:
- Association - Students take Courses (loose relationship)
- Aggregation - Departments have Professors (shared ownership)
- Composition - University has Departments (strong ownership)
Let's dive into the story.
The Story: Building a University System
Imagine we're building software for a university. We have:
- Students
- Professors
- Courses
- Departments
- The University itself
How do these objects relate to each other? That's where Association, Aggregation, and Composition come in.
1. Association: Students and Courses (Loose Relationship)
The Story:
Sarah is a student. She enrolls in "Python Programming" this semester. Next semester, she might take different courses. The course exists whether Sarah takes it or not. Sarah exists whether she's enrolled in courses or not.
Key Point: They have a relationship, but they're independent. One can exist without the other.
Code Example
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.courses = [] # Students can take courses
def enroll(self, course):
"""Associate student with a course"""
self.courses.append(course)
course.add_student(self)
print(f"{self.name} enrolled in {course.name}")
def drop(self, course):
"""Remove association"""
self.courses.remove(course)
course.remove_student(self)
print(f"{self.name} dropped {course.name}")
def __str__(self):
return f"Student: {self.name} (ID: {self.student_id})"
class Course:
def __init__(self, course_code, name):
self.course_code = course_code
self.name = name
self.students = [] # Courses can have students
def add_student(self, student):
"""Associate course with a student"""
self.students.append(student)
def remove_student(self, student):
"""Remove association"""
self.students.remove(student)
def list_students(self):
"""Show enrolled students"""
print(f"\n{self.name} - Enrolled Students:")
for student in self.students:
print(f" - {student.name}")
def __str__(self):
return f"Course: {self.name} ({self.course_code})"
# ===== Association in Action =====
# Create independent objects
sarah = Student("Sarah Johnson", "S001")
mike = Student("Mike Chen", "S002")
python_course = Course("CS101", "Python Programming")
java_course = Course("CS102", "Java Fundamentals")
# Create associations (relationships)
sarah.enroll(python_course)
sarah.enroll(java_course)
mike.enroll(python_course)
# Association is bidirectional
print(f"\n{sarah.name}'s courses: {[c.name for c in sarah.courses]}")
python_course.list_students()
# Objects can exist independently
sarah.drop(java_course) # Sarah drops a course, but course still exists
print(f"\n{sarah.name}'s courses after dropping: {[c.name for c in sarah.courses]}")
# Delete Sarah - courses still exist!
del sarah
print(f"\nCourse still exists: {python_course}")
Output:
Sarah Johnson enrolled in Python Programming
Sarah Johnson enrolled in Java Fundamentals
Mike Chen enrolled in Python Programming
Sarah Johnson's courses: ['Python Programming', 'Java Fundamentals']
Python Programming - Enrolled Students:
- Sarah Johnson
- Mike Chen
Sarah Johnson dropped Java Fundamentals
Sarah Johnson's courses after dropping: ['Python Programming']
Course still exists: Course: Python Programming (CS101)
Key Characteristic of Association:
- ✅ Weak relationship (can exist independently)
- ✅ Many-to-many possible (one student → many courses, one course → many students)
- ✅ Objects have separate lifecycles
- ✅ Deleting one doesn't delete the other
2. Aggregation: Department and Professors (Shared Ownership)
The Story:
Professor Smith teaches in the Computer Science Department. But if the CS Department closes, Professor Smith doesn't disappear—she can move to the Math Department or another university. The department "has" professors, but doesn't "own" them exclusively.
Key Point: The container (Department) has objects (Professors), but those objects can exist independently and be shared.
Code Example
class Professor:
def __init__(self, name, employee_id, specialization):
self.name = name
self.employee_id = employee_id
self.specialization = specialization
def __str__(self):
return f"Prof. {self.name} ({self.specialization})"
class Department:
def __init__(self, name, department_code):
self.name = name
self.department_code = department_code
self.professors = [] # Aggregation: has professors
def add_professor(self, professor):
"""Add professor to department (shared ownership)"""
self.professors.append(professor)
print(f"{professor.name} joined {self.name} Department")
def remove_professor(self, professor):
"""Remove professor (but professor still exists)"""
self.professors.remove(professor)
print(f"{professor.name} left {self.name} Department")
def list_professors(self):
"""Show department faculty"""
print(f"\n{self.name} Department Faculty:")
for prof in self.professors:
print(f" - {prof}")
def __del__(self):
"""When department is deleted, professors survive"""
print(f"\n{self.name} Department closed (but professors still exist)")
# ===== Aggregation in Action =====
# Create professors (exist independently)
prof_smith = Professor("Dr. Emily Smith", "P001", "Machine Learning")
prof_jones = Professor("Dr. Robert Jones", "P002", "Databases")
prof_wang = Professor("Dr. Lisa Wang", "P003", "Algorithms")
# Create department
cs_department = Department("Computer Science", "CS")
math_department = Department("Mathematics", "MATH")
# Aggregation: department has professors (shared ownership)
cs_department.add_professor(prof_smith)
cs_department.add_professor(prof_jones)
math_department.add_professor(prof_wang)
cs_department.list_professors()
# Professor can move to another department
cs_department.remove_professor(prof_smith)
math_department.add_professor(prof_smith) # Shared between departments!
math_department.list_professors()
# Delete department - professors still exist!
del cs_department # Department closes
print(f"\nProfessors still exist after department closure:")
print(f" - {prof_smith}")
print(f" - {prof_jones}")
print(f" - {prof_wang}")
Output:
Dr. Emily Smith joined Computer Science Department
Dr. Robert Jones joined Computer Science Department
Dr. Lisa Wang joined Mathematics Department
Computer Science Department Faculty:
- Prof. Dr. Emily Smith (Machine Learning)
- Prof. Dr. Robert Jones (Databases)
Dr. Emily Smith left Computer Science Department
Dr. Emily Smith joined Mathematics Department
Mathematics Department Faculty:
- Prof. Dr. Lisa Wang (Algorithms)
- Prof. Dr. Emily Smith (Machine Learning)
Computer Science Department closed (but professors still exist)
Professors still exist after department closure:
- Prof. Dr. Emily Smith (Machine Learning)
- Prof. Dr. Robert Jones (Databases)
- Prof. Dr. Lisa Wang (Algorithms)
Key Characteristic of Aggregation:
- ✅ "Has-a" relationship with shared ownership
- ✅ Child can exist independently of parent
- ✅ Child can belong to multiple parents
- ✅ Deleting parent doesn't delete child
3. Composition: University and Departments (Strong Ownership)
The Story:
Harvard University has a Computer Science Department. If Harvard closes (hypothetically), the Computer Science Department of Harvard ceases to exist. You can't move "Harvard's CS Department" to MIT—it's fundamentally part of Harvard.
Key Point: The whole (University) owns the parts (Departments). If the whole is destroyed, the parts are destroyed too.
Code Example
class Department:
def __init__(self, name, department_code):
self.name = name
self.department_code = department_code
print(f" Created: {self.name} Department ({self.department_code})")
def __del__(self):
print(f" Destroyed: {self.name} Department")
def __str__(self):
return f"{self.name} Department ({self.department_code})"
class University:
def __init__(self, name):
self.name = name
self.departments = [] # Composition: owns departments
print(f"\n{self.name} University founded!")
def create_department(self, name, code):
"""Composition: University creates and owns departments"""
dept = Department(name, code)
self.departments.append(dept)
return dept
def list_departments(self):
print(f"\n{self.name} Departments:")
for dept in self.departments:
print(f" - {dept}")
def __del__(self):
"""When university is destroyed, departments are destroyed too"""
print(f"\n{self.name} University closed!")
print("All departments are being destroyed:")
# Explicitly delete departments (composition)
for dept in self.departments:
del dept
# ===== Composition in Action =====
# Create university
harvard = University("Harvard")
# Composition: University creates its own departments
# Departments cannot exist without the university
cs_dept = harvard.create_department("Computer Science", "CS")
eng_dept = harvard.create_department("Engineering", "ENG")
med_dept = harvard.create_department("Medicine", "MED")
harvard.list_departments()
# Try to create standalone department outside university
# This breaks the composition principle
print("\n--- What if we try to share a department? ---")
# You CAN'T do: stanford.add_existing_department(cs_dept)
# Because cs_dept is OWNED by harvard, not shared!
# When university is deleted, ALL departments die with it
print("\n--- University closing... ---")
del harvard # Destroys university AND all its departments
# Departments no longer exist
print("\n--- After university closure ---")
print("Departments are gone (destroyed with university)")
Output:
Harvard University founded!
Created: Computer Science Department (CS)
Created: Engineering Department (ENG)
Created: Medicine Department (MED)
Harvard Departments:
- Computer Science Department (CS)
- Engineering Department (ENG)
- Medicine Department (MED)
--- What if we try to share a department? ---
--- University closing... ---
Harvard University closed!
All departments are being destroyed:
Destroyed: Computer Science Department
Destroyed: Engineering Department
Destroyed: Medicine Department
--- After university closure ---
Departments are gone (destroyed with university)
Key Characteristic of Composition:
- ✅ "Has-a" relationship with exclusive ownership
- ✅ Child cannot exist without parent
- ✅ Child cannot be shared between parents
- ✅ Deleting parent automatically deletes child
- ✅ Strongest form of relationship
Visual Summary: The Three Relationships
ASSOCIATION (Loose - Independent)
┌─────────┐ ┌─────────┐
│ Student │ ←──────→ │ Course │
└─────────┘ └─────────┘
Can exist independently
Student can drop course, course continues
AGGREGATION (Shared - Weak Ownership)
┌────────────┐ ┌───────────┐
│ Department │ ←──────→ │ Professor │
└────────────┘ └───────────┘
Professor can exist without department
Professor can move to another department
COMPOSITION (Strong - Exclusive Ownership)
┌────────────┐
│ University │
└─────┬──────┘
│ owns (creates & destroys)
▼
┌────────────┐
│ Department │ ← Cannot exist without University
└────────────┘
Real-World Examples to Remember
Association Examples:
- Student ↔ Course
- Customer ↔ Product (shopping)
- Driver ↔ Car (rental)
- Doctor ↔ Patient
Aggregation Examples:
- Department ↔ Professor
- Company ↔ Employee
- Team ↔ Player
- Library ↔ Book
Composition Examples:
- University → Department
- House → Room
- Car → Engine
- Human → Heart
Decision Tree: Which Relationship to Use?
Ask yourself:
1. Can object B exist without object A?
- ✅ Yes → Association or Aggregation
- ❌ No → Composition
2. Can object B belong to multiple A's simultaneously?
- ✅ Yes → Association
- ❌ No → Aggregation or Composition
3. If A is destroyed, should B be destroyed too?
- ✅ Yes → Composition
- ❌ No → Aggregation
Common Mistakes to Avoid
❌ Mistake 1: Using Composition When You Need Aggregation
# WRONG: Engine shouldn't be owned by Car exclusively
class Car:
def __init__(self):
self.engine = Engine() # Composition
def replace_engine(self, new_engine):
self.engine = new_engine # Contradiction!
If you can "replace" something, it's Aggregation, not Composition.
❌ Mistake 2: Using Association When You Need Composition
# WRONG: Room should be owned by House
class House:
def __init__(self):
self.rooms = []
def add_room(self, room): # Makes room independent
self.rooms.append(room)
# Room can exist without house? Doesn't make sense!
❌ Mistake 3: Confusing Aggregation and Composition
Ask: "If I delete the container, should the contents disappear?"
- Yes → Composition (House → Room)
- No → Aggregation (Department → Professor)
Quick Reference Cheat Sheet
| Feature | Association | Aggregation | Composition |
|---|---|---|---|
| Relationship | Uses | Has (shared) | Has (owned) |
| Dependency | Independent | Weak | Strong |
| Lifecycle | Separate | Child survives | Child dies with parent |
| Sharing | Many-to-many | Possible | Not possible |
| Example | Student-Course | Dept-Professor | Univ-Department |
Conclusion: The University Story You'll Never Forget
Think of our university system:
Association: Students take courses (independent schedules)
Aggregation: Departments have professors (professors can transfer)
Composition: University owns departments (departments are part of the university)
Next time you're designing a system, ask:
- Is this a loose relationship? → Association
- Can this be shared but has some ownership? → Aggregation
- Is this an integral part that can't exist alone? → Composition
Understanding these relationships is the difference between brittle, tightly-coupled code and flexible, maintainable architecture.
Finally understand the difference? 👏 Clap if the university story made it click! (50 claps available!)
Want more OOP deep dives? 🔔 Follow me for design patterns, SOLID principles, and clean architecture tutorials.
Challenge for you: 💬 Comment with your own example of Association, Aggregation, or Composition from your projects!
Share the knowledge! 📤 Send this to developers confused about OOP relationships - the university story makes it so clear!
Tags: #OOP #Python #Association #Aggregation #Composition #ObjectOrientedProgramming #SoftwareDesign #Programming #Coding #SoftwareEngineering
Top comments (0)