Composition — Has-A with Dependent Lifecycle
Introduction
A University has Departments. Not just any departments — departments that were created by the university, exist because of the university, and will cease to exist if the university shuts down. The "Computer Science Department" at MIT is not a free-floating entity that was assigned to MIT — it's an intrinsic part of MIT. If MIT closes, the MIT CS Department no longer exists.
This is Composition: the strongest form of "has-a" relationship. The whole owns its parts, creates them, and controls their lifecycle. When the whole dies, the parts die with it.
In the previous tutorial, we saw Aggregation — where a Department has Professors but doesn't control their existence. Composition tightens this: the whole owns the parts exclusively, and their lives are bound together.
Composition is one of the most powerful concepts in object-oriented design. The famous principle "favor composition over inheritance" (covered later in this section) is built entirely on this relationship. Understanding it deeply is prerequisite to mastering design patterns in S10–S12.
What Makes Composition Different from Aggregation?
The critical difference comes down to lifecycle dependency and exclusive ownership:
Aggregation (◇ open diamond): "I have parts, but they can exist without me."
- Department ◇→ Professor: professors survive department dissolution
- Parts are created externally and passed in
- Parts can be shared across multiple wholes
Composition (◆ filled diamond): "I own parts that cannot exist without me."
- University ◆→ Department: departments are destroyed when the university closes
- Parts are created by the whole (or at least for the whole)
- Parts belong to exactly ONE whole — no sharing
Real-world examples that clarify the distinction:
- A house has rooms → Composition. If the house is demolished, the rooms cease to exist. A room doesn't float to another house.
- A car has an engine → Composition. The engine was built as part of this car. If the car is scrapped, the engine goes with it.
- An order has order items → Composition. "Item #3 of Order #5001" has no meaning without Order #5001.
- A human body has a heart → Composition. The heart doesn't exist independently.
Contrast with Aggregation:
- A parking lot has cars → Aggregation. Cars drive away independently.
- A team has players → Aggregation. Players join other teams.
UML Notation

Visualization

Code Example
Two examples demonstrate Composition clearly:
- University → Department: departments are created by the university and destroyed with it
- Order → OrderItem: order items are created within an order and have no independent existence
Notice the key code pattern: the whole creates the parts internally (unlike Aggregation, where parts are passed in from outside).
from typing import List
class Department:
"""A department that exists only as part of a University."""
def __init__(self, name: str, budget: float):
self._name = name
self._budget = budget
self._is_active = True
@property
def name(self) -> str:
return self._name
@property
def is_active(self) -> bool:
return self._is_active
def deactivate(self) -> None:
"""Called when the university destroys this department."""
self._is_active = False
print(f" {self._name} department deactivated")
def __repr__(self) -> str:
status = "active" if self._is_active else "INACTIVE"
return f"{self._name} [{status}]"
class University:
"""
University COMPOSES departments.
Key differences from Aggregation:
1. Departments are CREATED by the university (not passed in)
2. Departments are DESTROYED when the university shuts down
3. Departments belong to exactly ONE university
"""
def __init__(self, name: str):
self._name = name
self._departments: List[Department] = []
def create_department(self, dept_name: str, budget: float) -> Department:
"""University CREATES the department — composition pattern."""
dept = Department(dept_name, budget) # Created BY the whole
self._departments.append(dept)
print(f" {self._name} created {dept_name} department")
return dept
def shutdown(self) -> None:
"""Shut down the university — ALL departments are destroyed."""
print(f"\n {self._name} is shutting down...")
for dept in self._departments:
dept.deactivate() # Parts destroyed with the whole
self._departments.clear()
print(f" {self._name} shutdown complete. All departments gone.")
def __repr__(self) -> str:
dept_names = [d.name for d in self._departments]
return f"{self._name} University (departments: {dept_names})"
# --- Order/OrderItem example ---
class OrderItem:
"""An item that exists only within an Order."""
def __init__(self, product_name: str, quantity: int, unit_price: float):
self._product_name = product_name
self._quantity = quantity
self._unit_price = unit_price
def get_subtotal(self) -> float:
return self._quantity * self._unit_price
def __repr__(self) -> str:
return f"{self._product_name} x{self._quantity} @ ${self._unit_price:.2f}"
class Order:
"""Order COMPOSES OrderItems — items are created BY and destroyed WITH the order."""
def __init__(self, order_id: str):
self._order_id = order_id
self._items: List[OrderItem] = []
self._status = "ACTIVE"
def add_item(self, product_name: str, quantity: int, unit_price: float) -> None:
"""Order CREATES the item internally — composition pattern."""
item = OrderItem(product_name, quantity, unit_price) # Created BY the whole
self._items.append(item)
print(f" Added {item} to order {self._order_id}")
def get_total(self) -> float:
return sum(item.get_subtotal() for item in self._items)
def cancel(self) -> None:
"""Cancel order — ALL items are destroyed."""
print(f" Cancelling order {self._order_id}...")
self._items.clear() # Items destroyed with the order
self._status = "CANCELLED"
print(f" Order {self._order_id} cancelled. All items removed.")
def __repr__(self) -> str:
return f"Order {self._order_id} [{self._status}] ({len(self._items)} items, total: ${self.get_total():.2f})"
# Demonstration
print("=== University Composition ===")
mit = University("MIT")
mit.create_department("Computer Science", 500_000)
mit.create_department("Mathematics", 300_000)
mit.create_department("Physics", 400_000)
print(f" {mit}")
mit.shutdown() # All departments destroyed
print("\n=== Order Composition ===")
order = Order("ORD-5001")
order.add_item("Laptop", 1, 999.99)
order.add_item("Mouse", 2, 29.99)
order.add_item("USB Cable", 3, 9.99)
print(f" {order}")
order.cancel() # All items destroyed
print(f" {order}")import java.util.ArrayList;
import java.util.List;
class Department {
private final String name;
private final double budget;
private boolean active = true;
public Department(String name, double budget) {
this.name = name;
this.budget = budget;
}
public String getName() {
return name;
}
public void deactivate() {
active = false;
System.out.println(" " + name + " department deactivated");
}
@Override
public String toString() {
return name + " [" + (active ? "active" : "INACTIVE") + "]";
}
}
class University {
private final String name;
private final List<Department> departments = new ArrayList<>();
public University(String name) {
this.name = name;
}
// University CREATES the department — composition pattern
public Department createDepartment(String deptName, double budget) {
Department dept = new Department(deptName, budget); // Created BY the whole
departments.add(dept);
System.out.println(" " + name + " created " + deptName + " department");
return dept;
}
// Shutdown destroys ALL departments
public void shutdown() {
System.out.println("\n " + name + " is shutting down...");
for (Department dept : departments) {
dept.deactivate(); // Parts destroyed with the whole
}
departments.clear();
System.out.println(" " + name + " shutdown complete. All departments gone.");
}
@Override
public String toString() {
List<String> names = departments.stream().map(Department::getName).toList();
return name + " University (departments: " + names + ")";
}
}
class OrderItem {
private final String productName;
private final int quantity;
private final double unitPrice;
public OrderItem(String productName, int quantity, double unitPrice) {
this.productName = productName;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public double getSubtotal() {
return quantity * unitPrice;
}
@Override
public String toString() {
return productName + " x" + quantity + " @ $" + String.format("%.2f", unitPrice);
}
}
class Order {
private final String orderId;
private final List<OrderItem> items = new ArrayList<>();
private String status = "ACTIVE";
public Order(String orderId) {
this.orderId = orderId;
}
// Order CREATES the item — composition pattern
public void addItem(String productName, int quantity, double unitPrice) {
OrderItem item = new OrderItem(productName, quantity, unitPrice);
items.add(item);
System.out.println(" Added " + item + " to order " + orderId);
}
public double getTotal() {
return items.stream().mapToDouble(OrderItem::getSubtotal).sum();
}
public void cancel() {
System.out.println(" Cancelling order " + orderId + "...");
items.clear(); // Items destroyed with the order
status = "CANCELLED";
System.out.println(" Order " + orderId + " cancelled. All items removed.");
}
@Override
public String toString() {
return "Order " + orderId + " [" + status + "] (" + items.size() + " items, total: $" + String.format("%.2f", getTotal()) + ")";
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== University Composition ===");
University mit = new University("MIT");
mit.createDepartment("Computer Science", 500_000);
mit.createDepartment("Mathematics", 300_000);
mit.createDepartment("Physics", 400_000);
System.out.println(" " + mit);
mit.shutdown(); // All departments destroyed
System.out.println("\n=== Order Composition ===");
Order order = new Order("ORD-5001");
order.addItem("Laptop", 1, 999.99);
order.addItem("Mouse", 2, 29.99);
order.addItem("USB Cable", 3, 9.99);
System.out.println(" " + order);
order.cancel(); // All items destroyed
System.out.println(" " + order);
}
}Composition vs Aggregation — The Code Pattern Difference
There's a recognizable pattern in code that distinguishes Composition from Aggregation:
Aggregation pattern — parts passed IN from outside:
dept = Department("CS", 500000)
prof = Professor("P001", "Smith", "Algorithms") # Created independently
dept.add_professor(prof) # Passed INTO the department
Composition pattern — parts created BY the whole:
university = University("MIT")
university.create_department("CS", 500000) # Created BY the university
In Aggregation, the caller creates the part and passes it in. In Composition, the whole creates the part internally. This is a reliable heuristic for identifying which relationship you're looking at in code.
Another code signal: Look at what happens in the destructor or cleanup method:
- Aggregation: the whole clears its references but doesn't destroy parts
- Composition: the whole actively destroys/deactivates its parts
Common Mistakes
Mistake 1: Confusing Composition with "Has-a" in General
The problem: Treating every "has-a" relationship as Composition. "A car has a driver" — is that Composition? No. The driver exists independently of the car.
The rule: Composition requires lifecycle dependency. The part must be meaningless or non-existent without the whole. "A car has an engine" → Composition (engine is integral). "A car has a driver" → Association or Aggregation.
Mistake 2: Using Composition When Parts Should Be Shared
The problem: Modeling Department ◆→ Professor as Composition. But professors can belong to multiple departments (joint appointments), and they survive department restructuring.
The fix: If parts can be shared or survive the whole's destruction → use Aggregation (◇), not Composition (◆).
Mistake 3: Not Cleaning Up Parts When the Whole is Destroyed
The problem: You model Composition in UML but don't enforce it in code. The Order is deleted but OrderItems linger in the database as orphans.
The fix: Composition is a design contract. If you use ◆ in UML, your code must cascade deletion. In databases, this maps to ON DELETE CASCADE foreign key constraints.
Key Takeaways
- Composition is the strongest "has-a" relationship — the whole owns its parts and controls their lifecycle
- When the whole is destroyed, all parts are destroyed too — this is the defining characteristic
- Parts are typically created by the whole (not passed in from outside)
- Parts belong to exactly one whole — no shared membership
- UML notation: filled (solid) diamond ◆ on the whole side
- The code pattern: whole creates parts internally (
university.create_department()), not externally passed in - Composition is the foundation of the "favor composition over inheritance" principle (covered later in S4)
- In database design, Composition maps to
ON DELETE CASCADE— reinforcing lifecycle dependency - You'll use Composition extensively in design problems: Order ◆→ OrderItem, House ◆→ Room, Car ◆→ Engine