Python Aggregation vs Composition

I have spent many years building scalable applications in Python, and one thing I’ve learned is that how you structure your classes matters more than the code inside them.

When I first started, I often struggled with “Has-A” relationships. I would frequently mix up when to use Aggregation and when to use Composition.

Through years of trial and error in enterprise environments, I realized that choosing the wrong one can lead to messy code that is incredibly hard to maintain.

In this tutorial, I will walk you through the practical differences between Aggregation and Composition using real-world examples you’d encounter in a typical business application.

Understand Object Relationships in Python

In Object-Oriented Programming (OOP), we often talk about inheritance, which is an “Is-A” relationship. For example, a “Tesla” is a “Car.”

However, most of our daily coding involves “Has-A” relationships. This is where one class contains an instance of another class.

Aggregation and Composition are the two primary ways we handle these “Has-A” relationships. The main difference lies in the “ownership” and the “lifecycle” of the objects.

Implement Composition in Python

In my experience, Composition is the “stronger” of the two relationships. In Composition, the child object cannot exist independently of the parent object.

If the parent object is destroyed, the child object is also deleted. I like to think of this as a “Part-of” relationship.

A Real-World Example: A Silicon Valley Startup Office

Imagine I am building a management system for a tech startup in San Francisco. Every Office has several Department units (like Engineering, Marketing, and HR).

If the Office closes down permanently, those specific Department entities within that office structure cease to exist in our system.

Here is how I would write this in Python:

class Department:
    def __init__(self, name, budget):
        self.name = name
        self.budget = budget

    def __str__(self):
        return f"{self.name} Department with a budget of ${self.budget:,}"

class Office:
    def __init__(self, location):
        self.location = location
        # The Department objects are created INSIDE the Office
        # This is the hallmark of Composition
        self.departments = [
            Department("Engineering", 500000),
            Department("Marketing", 200000)
        ]

    def show_office_details(self):
        print(f"Office Location: {self.location}")
        for dept in self.departments:
            print(f" - {dept}")

    def __del__(self):
        print(f"Closing the {self.location} office and dissolving all departments.")

# Usage
my_office = Office("Palo Alto, CA")
my_office.show_office_details()

# When we delete the office, the departments go with it
del my_office

You can see the output in the screenshot below.

Python Aggregation vs Composition

In this code, notice that I instantiated the Department objects inside the __init__ method of the Office.

This ensures that the Office owns the lifecycle of the departments. If my_office is gone, the departments are gone too.

Implement Aggregation in Python

Aggregation is what I call a “weak” relationship. Here, the child object can exist independently of the parent.

If the parent is destroyed, the child continues to live on. This is more of a “Uses-A” or “Associated-With” relationship.

A Real-World Example: A New York Law Firm and its Attorneys

Let’s look at a New York-based law firm. A LawFirm has many Attorney objects.

If the LawFirm goes bankrupt and dissolves, the Attorney doesn’t stop existing. They simply move to another firm or start a solo practice.

Here is the implementation of Aggregation:

class Attorney:
    def __init__(self, name, specialty):
        self.name = name
        self.specialty = specialty

    def __str__(self):
        return f"Attorney {self.name}, specializing in {self.specialty}"

class LawFirm:
    def __init__(self, firm_name):
        self.firm_name = firm_name
        self.attorneys = []

    def hire_attorney(self, attorney):
        # The attorney is created OUTSIDE and passed IN
        # This is the hallmark of Aggregation
        self.attorneys.append(attorney)

    def show_firm_roster(self):
        print(f"Firm Name: {self.firm_name}")
        if not self.attorneys:
            print(" No attorneys currently on staff.")
        for lawyer in self.attorneys:
            print(f" - {lawyer}")

# Create the attorneys independently
lawyer1 = Attorney("Harvey Specter", "Corporate Law")
lawyer2 = Attorney("Jessica Pearson", "Commercial Litigation")

# Create the firm and add attorneys
pearson_hardman = LawFirm("Pearson Hardman - NYC")
pearson_hardman.hire_attorney(lawyer1)
pearson_hardman.hire_attorney(lawyer2)

pearson_hardman.show_firm_roster()

# Delete the firm
print("\nDissolving the law firm...")
del pearson_hardman

# The attorneys still exist!
print(f"Checking attorney status: {lawyer1.name} is still available for hire.")

You can see the output in the screenshot below.

Aggregation vs Composition in Python

As you can see, I created lawyer1 outside of the LawFirm class. Even after I deleted pearson_hardman, I could still access lawyer1.

Key Differences I’ve Noticed in Production

Over the years, I have developed a few “rules of thumb” to help me decide which one to use.

1. The Lifetime Test I always ask myself: “If the container dies, does the contained item die too?” If yes, I use Composition. If no, I use Aggregation.

2. Creation Responsibility In Composition, the parent is responsible for creating the child. In Aggregation, the child is often created elsewhere and handed to the parent.

3. Dependency Level Composition creates a very tight coupling. I use it when the parts have no meaning outside the whole (like pages in a book).

Aggregation is loosely coupled. I use it for collections of independent items (like cars in a parking lot in Chicago).

When to Use Composition (Method 1)

I use Composition when I want to ensure encapsulation and data integrity. It’s perfect for complex objects made of smaller, specialized parts.

For example, when I worked on an e-commerce platform for a retail giant in Texas, we used Composition for an Order and its LineItems.

An OrderLineItem makes no sense without an Order. By using Composition, I ensured that no orphan line items could exist in the database.

class LineItem:
    def __init__(self, product_id, price):
        self.product_id = product_id
        self.price = price

class Order:
    def __init__(self, order_id):
        self.order_id = order_id
        self.items = []

    def add_item(self, product_id, price):
        # Composition: The Order creates the LineItem
        new_item = LineItem(product_id, price)
        self.items.append(new_item)

When to Use Aggregation (Method 2)

I prefer Aggregation when I need to share objects between different parents.

Think of a University and a Professor. A professor might teach at multiple universities or research centers across the US.

If I used Composition, deleting one university would “delete” the professor, which would break the data for the other university.

class Professor:
    def __init__(self, name):
        self.name = name

class University:
    def __init__(self, name):
        self.name = name
        self.faculty = []

    def add_faculty(self, professor):
        # Aggregation: The Professor is passed in
        self.faculty.append(professor)

# One professor can be part of multiple universities
prof_smith = Professor("Dr. Smith")
ucla = University("UCLA")
usc = University("USC")

ucla.add_faculty(prof_smith)
usc.add_faculty(prof_smith)

Common Issues to Avoid

I’ve seen many developers default to Aggregation because it feels “safer” and less restrictive. However, this often leads to “leaky abstractions.”

If your class is supposed to “own” its components, use Composition. It makes your code more predictable and easier to debug.

Another mistake is overcomplicating the relationship. If you don’t need the child to exist outside the parent, don’t let it.

Keep your logic as local as possible. It will save you hours of tracing object references later on.

I hope this guide helps you understand when to use Aggregation and when to use Composition in your Python projects.

It took me a while to get the hang of these concepts, but once I did, my code became much cleaner and more professional.

The best way to learn is to try implementing these in your own USA-based business logic examples.

You may also like to read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.