OOP Interview Prep
Object Relationships

Aggregation

The "Has-a" Relationship Where Objects Live Their Own Lives

LinkedIn Hook

Here is a question that trips up a lot of mid-level candidates:

"What is the difference between aggregation and composition?"

Most people say something like "they're both has-a relationships" and then stall. The interviewer waits. Silence.

The real answer lives in one word: lifecycle. In aggregation, the parts survive when the container is destroyed. In composition, they don't. A teacher still exists after a department is disbanded. A room does not exist after a house is demolished.

That distinction sounds simple. But candidates who can explain it clearly, back it with code, and connect it to real design decisions are the ones who get the follow-up questions — because the interviewer wants to keep talking to them.

This lesson covers aggregation completely: what it is, what it is not, and how to explain it so it sticks.

Read the full lesson with code and comparison notes -> [link]

#OOP #JavaScript #SoftwareEngineering #InterviewPrep #ObjectRelationships


Aggregation thumbnail


What You'll Learn

  • The precise definition of aggregation and why it is called "weak ownership" in OOP
  • Why lifecycle independence is the single most important idea in aggregation
  • How to model aggregation correctly in JavaScript using object references
  • The clear line between aggregation and composition, explained in code and plain language
  • How to answer the classic Department-Teacher interview question without hesitation

The Analogy That Makes It Click

Think about a university department and its teachers.

The Computer Science department has teachers. Dr. Patel teaches algorithms. Dr. Lee teaches databases. The department has those teachers — that is a "has-a" relationship. But here is the critical detail: if the university shuts down the Computer Science department tomorrow, Dr. Patel and Dr. Lee do not cease to exist. They move to another department, take a position at a different university, or start consulting. Their existence was never tied to the department's existence.

The department owned them in a loose sense. It organized them, gave them context, grouped them together. But it did not create them, and destroying it does not destroy them. The ownership is weak.

That is aggregation.

Now compare it to a different relationship: a university department and its budget. When the department is dissolved, the budget is dissolved with it. The budget cannot float off and attach to another department independently. The budget belongs entirely to one department, and its lifecycle is tied to that department's lifecycle.

That is composition (covered in Lesson 6.3).

The shape of the code reflects the shape of the real-world relationship. Aggregation uses object references passed in from the outside. Composition creates objects internally. That structural choice is not accidental — it is a deliberate modeling decision.

[INTERNAL-LINK: lifecycle dependency in composition -> Lesson 6.3: Composition]


What Is Aggregation?

Aggregation is a "has-a" relationship where the container holds references to objects that can exist independently of the container. The contained objects are created outside the container and passed in. If the container is destroyed, the contained objects continue to exist. The ownership is weak.

The key phrase in every aggregation definition is "lifecycle independence." The parts have their own lifecycle, separate from the whole that temporarily holds them.

In code terms: the container stores a reference to an object it did not create. When the container is garbage-collected, the reference disappears — but the original object can still be referenced by something else. Nothing about the container's destruction forces the contained object out of memory.

This is the defining difference from composition. In composition, the container creates the contained objects itself. Destroying the container removes the last reference to those objects, and they are collected. In aggregation, the contained object was alive before the container and may remain alive after.

[INTERNAL-LINK: the broader landscape of object relationships -> Lesson 6.1: Association]


Code Example 1 — Basic Aggregation: Department and Teacher

This example shows the core pattern. Teachers are created independently. A department holds references to them. Destroying the department does not destroy the teachers.

// Teacher exists independently — it is not created inside any Department
class Teacher {
  constructor(name, subject) {
    this.name = name;
    this.subject = subject;
  }

  teach() {
    console.log(`${this.name} is teaching ${this.subject}.`);
  }

  introduce() {
    return `I am ${this.name}, I teach ${this.subject}.`;
  }
}

// Department holds Teacher references — weak ownership
// Teachers are passed in, not created here
class Department {
  constructor(name) {
    this.name = name;
    this.teachers = []; // will hold references to independent Teacher objects
  }

  // Receive an existing Teacher object — aggregation pattern
  addTeacher(teacher) {
    this.teachers.push(teacher);
    console.log(`${teacher.name} added to ${this.name} department.`);
  }

  // Remove a Teacher — the Teacher object itself is not destroyed
  removeTeacher(teacherName) {
    const before = this.teachers.length;
    this.teachers = this.teachers.filter(t => t.name !== teacherName);
    const removed = before - this.teachers.length;
    if (removed > 0) {
      console.log(`${teacherName} removed from ${this.name} department.`);
    }
  }

  listTeachers() {
    console.log(`\n${this.name} Department Teachers:`);
    this.teachers.forEach(t => console.log(`  - ${t.introduce()}`));
  }
}

// Teachers are created independently — outside any Department
const drPatel = new Teacher("Dr. Patel", "Algorithms");
const drLee = new Teacher("Dr. Lee", "Databases");
const drKhan = new Teacher("Dr. Khan", "Networks");

// Department is created separately
const csDepartment = new Department("Computer Science");

// Teachers are assigned to the department — not created by it
csDepartment.addTeacher(drPatel);
csDepartment.addTeacher(drLee);
csDepartment.addTeacher(drKhan);

csDepartment.listTeachers();
// Computer Science Department Teachers:
//   - I am Dr. Patel, I teach Algorithms.
//   - I am Dr. Lee, I teach Databases.
//   - I am Dr. Khan, I teach Networks.

// Department is "dissolved" — we null out the reference
let dissolvedDept = csDepartment;
dissolvedDept = null; // simulating destruction of the department

// KEY POINT: The Teacher objects still exist independently
// They were not created by the department, so nulling the department
// does not affect them at all
console.log("\nAfter department is dissolved:");
drPatel.teach(); // Dr. Patel is teaching Algorithms.
drLee.teach();   // Dr. Lee is teaching Databases.
drKhan.teach();  // Dr. Khan is teaching Networks.

// Teachers can be reassigned to a completely different department
const mathDepartment = new Department("Mathematics");
mathDepartment.addTeacher(drPatel); // Dr. Patel moves to a new department

mathDepartment.listTeachers();
// Mathematics Department Teachers:
//   - I am Dr. Patel, I teach Algorithms.

Notice what happened at the end. drPatel moved from Computer Science to Mathematics without any reconstruction. The teacher object was simply referenced by a new container. This is lifecycle independence in action: the teacher's state and existence are not controlled by any single department.


Code Example 2 — One Teacher, Multiple Departments

Aggregation allows a single object to be referenced by multiple containers simultaneously. This is impossible in composition (where one container owns and creates the object). This example shows a teacher serving two departments at once, which is a common real-world scenario.

class Teacher {
  constructor(name, expertise) {
    this.name = name;
    this.expertise = expertise;
    this.departments = []; // track which departments this teacher belongs to
  }

  joinDepartment(deptName) {
    this.departments.push(deptName);
  }

  leaveDepartment(deptName) {
    this.departments = this.departments.filter(d => d !== deptName);
  }

  status() {
    if (this.departments.length === 0) {
      return `${this.name} is currently unaffiliated.`;
    }
    return `${this.name} is active in: ${this.departments.join(", ")}.`;
  }
}

class Department {
  constructor(name) {
    this.name = name;
    this.teachers = [];
  }

  enroll(teacher) {
    this.teachers.push(teacher);
    teacher.joinDepartment(this.name); // teacher tracks membership too
    console.log(`${teacher.name} enrolled in ${this.name}.`);
  }

  dismiss(teacher) {
    this.teachers = this.teachers.filter(t => t !== teacher);
    teacher.leaveDepartment(this.name);
    console.log(`${teacher.name} dismissed from ${this.name}.`);
  }

  headcount() {
    return `${this.name} has ${this.teachers.length} teacher(s).`;
  }
}

// Create teachers independently
const drRoberts = new Teacher("Dr. Roberts", "Statistics");
const drSingh = new Teacher("Dr. Singh", "Machine Learning");

// Create departments independently
const dataDept = new Department("Data Science");
const mathDept = new Department("Mathematics");
const csDept = new Department("Computer Science");

// Aggregation: same teacher object referenced by multiple departments
dataDept.enroll(drRoberts);
mathDept.enroll(drRoberts); // Dr. Roberts belongs to two departments simultaneously
csDept.enroll(drSingh);
dataDept.enroll(drSingh);   // Dr. Singh also serves two departments

console.log("\nCurrent status:");
console.log(drRoberts.status()); // Dr. Roberts is active in: Data Science, Mathematics.
console.log(drSingh.status());   // Dr. Singh is active in: Computer Science, Data Science.
console.log(dataDept.headcount()); // Data Science has 2 teacher(s).

// Dissolve Data Science department
dataDept.dismiss(drRoberts);
dataDept.dismiss(drSingh);

console.log("\nAfter Data Science is dissolved:");
console.log(drRoberts.status()); // Dr. Roberts is active in: Mathematics.
console.log(drSingh.status());   // Dr. Singh is active in: Computer Science.
// Both teachers still exist and are still active in their remaining departments
// The dissolution of dataDept did not affect the teacher objects themselves

[ORIGINAL DATA]: In real system design, shared references like this are common in many-to-many database relationships, event listener registries, and team membership systems. The aggregation pattern maps directly to a join table in a relational database: the "container" row is deleted, but the "part" rows remain untouched.


Aggregation visual 1


Code Example 3 — Aggregation vs Composition Side by Side

The clearest way to see the difference is to write both patterns for the same domain. A school has departments (aggregation — departments can outlive the school if it restructures) and a principal (composition — the principal's role ceases to exist if the school is closed and the role is not transferred anywhere independently).

// AGGREGATION: School holds a reference to an existing Principal contract
// But here we demonstrate with Department — the classic aggregation example

// COMPOSITION: Engine is created INSIDE Car and cannot exist without it
class Engine {
  constructor(type, horsepower) {
    this.type = type;
    this.horsepower = horsepower;
  }

  start() {
    return `${this.type} engine (${this.horsepower}hp) starting...`;
  }
}

// Composition: Car CREATES its own Engine — the engine's lifecycle is tied to the car
class CarWithComposition {
  constructor(make, engineType, horsepower) {
    this.make = make;
    // Engine is created HERE, inside the car — strong ownership
    this.engine = new Engine(engineType, horsepower);
  }

  drive() {
    return `${this.make}: ${this.engine.start()}`;
  }
}

// Aggregation: Garage RECEIVES existing Car objects — cars live independently
class Garage {
  constructor(location) {
    this.location = location;
    this.cars = []; // holds references — does not create the cars
  }

  parkCar(car) {
    this.cars.push(car);
    console.log(`${car.make} parked at ${this.location} garage.`);
  }

  removeCar(make) {
    const before = this.cars.length;
    this.cars = this.cars.filter(c => c.make !== make);
    if (this.cars.length < before) {
      console.log(`${make} left ${this.location} garage.`);
    }
  }

  status() {
    return `${this.location} garage holds ${this.cars.length} car(s).`;
  }
}

// Cars are created independently — before any garage
const tesla = new CarWithComposition("Tesla Model 3", "Electric", 350);
const bmw = new CarWithComposition("BMW M3", "Petrol", 503);

// Note: Each car's Engine was created INSIDE the car constructor (composition)
// The Engine objects are NOT accessible or reusable outside the car
console.log(tesla.drive()); // Tesla Model 3: Electric engine (350hp) starting...
console.log(bmw.drive());   // BMW M3: Petrol engine (503hp) starting...

// Garage aggregates cars — does not own their lifecycle
const downtownGarage = new Garage("Downtown");
const airportGarage = new Garage("Airport");

downtownGarage.parkCar(tesla);
downtownGarage.parkCar(bmw);
console.log(downtownGarage.status()); // Downtown garage holds 2 car(s).

// A car moves from one garage to another — possible because garage has weak ownership
downtownGarage.removeCar("Tesla Model 3");
airportGarage.parkCar(tesla);

console.log(downtownGarage.status()); // Downtown garage holds 1 car(s).
console.log(airportGarage.status());  // Airport garage holds 1 car(s).

// "Destroy" the downtown garage
let tempRef = downtownGarage;
tempRef = null;

// BMW still exists even though its garage was destroyed
console.log(bmw.drive()); // BMW M3: Petrol engine (503hp) starting...

// SUMMARY:
// Composition  -> Car creates Engine internally (Engine cannot exist without Car)
// Aggregation  -> Garage receives Car from outside (Car can exist without any Garage)

[UNIQUE INSIGHT]: The constructor is the best signal for which pattern you are using. If you see this.x = new X() inside a constructor, that is composition — the object creates its dependency. If you see this.x = x where x is a constructor parameter, that is aggregation — the dependency comes from outside and can outlive the holder. This rule is not absolute, but it holds for the vast majority of cases you will encounter in interviews and production code.


Code Example 4 — Lifecycle Independence Under Pressure

This example makes lifecycle independence explicit with a scenario interviewers use to test understanding. A company has teams (aggregation), and employees exist beyond any single team.

class Employee {
  constructor(id, name, role) {
    this.id = id;
    this.name = name;
    this.role = role;
    this.isActive = true;
  }

  work() {
    if (!this.isActive) return `${this.name} is no longer active.`;
    return `${this.name} (${this.role}) is working.`;
  }

  resign() {
    this.isActive = false;
    console.log(`${this.name} has resigned.`);
  }
}

class Team {
  constructor(teamName, projectCode) {
    this.teamName = teamName;
    this.projectCode = projectCode;
    this.members = []; // aggregation: employees are passed in, not created here
    this.active = true;
  }

  addMember(employee) {
    if (!this.active) {
      console.log(`Cannot add to disbanded team ${this.teamName}.`);
      return;
    }
    this.members.push(employee);
    console.log(`${employee.name} joined team ${this.teamName}.`);
  }

  disband() {
    this.active = false;
    this.members = []; // team no longer holds references
    console.log(`Team ${this.teamName} (${this.projectCode}) has been disbanded.`);
    // Note: the Employee objects themselves are NOT affected
    // They are still in memory, still referenced by the original variables
  }

  roster() {
    if (!this.active) return `${this.teamName} is disbanded.`;
    return `${this.teamName} members: ${this.members.map(e => e.name).join(", ")}`;
  }
}

// Create employees — independently, before any team exists
const emp1 = new Employee(1, "Alice", "Frontend Engineer");
const emp2 = new Employee(2, "Bob", "Backend Engineer");
const emp3 = new Employee(3, "Carol", "QA Engineer");

// Create a team and aggregate employees into it
const alphaTeam = new Team("Alpha", "PRJ-2025-001");
alphaTeam.addMember(emp1); // Alice joined team Alpha.
alphaTeam.addMember(emp2); // Bob joined team Alpha.
alphaTeam.addMember(emp3); // Carol joined team Alpha.

console.log(alphaTeam.roster());
// Alpha members: Alice, Bob, Carol

// Disband the team — project ended
alphaTeam.disband();
// Team Alpha (PRJ-2025-001) has been disbanded.

// Employees still exist — lifecycle is independent
console.log(emp1.work()); // Alice (Frontend Engineer) is working.
console.log(emp2.work()); // Bob (Backend Engineer) is working.
console.log(emp3.work()); // Carol (QA Engineer) is working.

// Employees can join a new team immediately
const betaTeam = new Team("Beta", "PRJ-2025-002");
betaTeam.addMember(emp1); // Alice joined team Beta.
betaTeam.addMember(emp2); // Bob joined team Beta.

console.log(betaTeam.roster());
// Beta members: Alice, Bob

// An employee can also resign independently of any team action
emp3.resign(); // Carol has resigned.
console.log(emp3.work()); // Carol is no longer active.
// Carol's resignation had nothing to do with any team being created or destroyed

[PERSONAL EXPERIENCE]: The most common mistake developers make when first learning aggregation is building the contained objects inside the constructor anyway, and then justifying it by saying "the objects are independent." The code structure tells the truth. If the container creates its dependencies, the lifecycle is tied together regardless of what comments say. Pass dependencies in from outside — that is the structural guarantee of lifecycle independence.


Aggregation visual 2


Aggregation vs Composition — The Key Differences

AGGREGATION vs COMPOSITION — COMPARISON TABLE
---------------------------------------------------------------------------
Feature               Aggregation                 Composition
---------------------------------------------------------------------------
Relationship type     "Has-a" (weak ownership)    "Has-a" (strong ownership)

Who creates           External code creates        Container creates the
the parts?            the parts and passes         parts inside its own
                      them in                      constructor

Lifecycle of          Independent — parts          Dependent — parts are
contained objects     survive container            destroyed with container
                      destruction                  destruction

Can parts exist       Yes — before and after       No — they only make
without container?    the container                sense inside the container

Constructor signal    this.x = x (passed in)       this.x = new X()
                                                   (created internally)

Shared reference?     Yes — same object can        No — the part belongs
                      be in multiple containers    to exactly one container

UML symbol            Open diamond on the          Filled/solid diamond
                      container side               on the container side

Real-world example    Department has Teachers      House has Rooms
                      (teachers outlive dept)      (rooms don't exist
                                                   without the house)

Another example       Playlist has Songs           Order has OrderItems
                      (songs exist outside         (order items are tied
                      any playlist)                to exactly one order)

JavaScript signal     Object reference passed      Object created with
                      to constructor or method     new inside constructor
---------------------------------------------------------------------------

[CHART: UML-style diagram showing two class pairs. Left pair: Department (open diamond) -> Teacher with label "0.." and annotation "Aggregation". Right pair: House (filled diamond) -> Room with label "1.." and annotation "Composition". Dark background (#0a0a1a), neon green (#00ff87) for class boxes, neon orange (#ff6b35) for labels.]


Common Mistakes

  • Creating objects inside the constructor and calling it aggregation. If the constructor calls new X() to create the part, that is composition, not aggregation. Aggregation means the part was created externally and handed to the container. The constructor signature tells the story: a constructor parameter that accepts an existing object signals aggregation.

  • Thinking aggregation and association are the same thing. Association is the broadest relationship: one object uses another. Aggregation is a specific kind of association with a "whole-part" structure and a directional ownership. All aggregations are associations, but not all associations are aggregations. A Driver using a Car is association. A Garage holding Car objects it groups and manages is aggregation.

  • Confusing "can be in multiple containers" with "no ownership." Aggregation still implies ownership in the OOP sense — the container is responsible for the part in some functional way. The distinction is that the ownership is weak: the container did not create the part and cannot prevent it from being used elsewhere. Saying aggregation means "no ownership at all" misses the structural relationship.

  • Forgetting lifecycle independence under questioning. Interviewers often follow up with "what happens to the teachers if the department is deleted?" If you pause on this, the answer is: nothing happens to them. They were created independently and continue to exist. Only the department's reference to them disappears.

  • Using aggregation when composition is the right model. Not every "has-a" relationship should be aggregated. If the parts genuinely cannot exist or be meaningfully used outside the container — like a bank account's transaction history — composition is more accurate. Modeling it as aggregation (passing in an external transaction list) would imply that the same transaction list could belong to multiple accounts, which breaks the domain model.


Interview Questions

Q: What is aggregation in OOP, and how does it differ from composition?

Both aggregation and composition are "has-a" relationships. The difference is ownership strength and lifecycle dependency. In aggregation, the contained objects are created externally and can exist before and after the container. In composition, the container creates the objects internally, and they cannot exist independently. A Department aggregates Teachers because teachers outlive departments. A House composes Rooms because rooms have no existence outside the house.

Q: How do you identify aggregation in code?

Look at the constructor. If the container accepts an existing object as a parameter and stores a reference to it (this.teacher = teacher), that is aggregation — the object was created elsewhere and can outlive this container. If the container creates the object internally (this.engine = new Engine()), that is composition. The source of the object's creation is the clearest structural signal of which relationship you are modeling.

Q: Can one object participate in aggregation with multiple containers at the same time?

Yes, and this is one of the defining characteristics of aggregation. Because the aggregated object exists independently and is referenced by pointer, multiple containers can hold a reference to the same object simultaneously. A teacher can belong to two departments. A song can appear in multiple playlists. A developer can be a member of multiple teams. This shared-reference pattern is impossible in composition, where the object is owned and created by exactly one container.

Q: What is the UML representation of aggregation vs composition?

Aggregation uses an open (hollow) diamond on the side of the container, pointing toward the contained class. Composition uses a filled (solid) diamond. Both diamonds sit at the "whole" end of the relationship line. The open diamond signals weak ownership (the part can exist independently). The filled diamond signals strong ownership (the part cannot exist without the whole). Most interviewers who ask about UML are testing whether you know the diamond color distinction.

Q: Give a real-world example of aggregation that is not Department-Teacher.

A playlist and its songs is a strong example. A playlist groups songs, but the songs exist in the music library independently of any playlist. You can delete a playlist without deleting its songs. One song can appear in multiple playlists. The playlist aggregates songs — weak ownership, lifecycle independence, shared references possible. Another example: a shopping cart and products. The cart holds references to product objects, but the products exist in the catalog independently and remain after the cart is cleared or deleted.


Quick Reference Cheat Sheet

AGGREGATION — QUICK REFERENCE
==========================================================================

DEFINITION
----------
A "has-a" relationship with WEAK ownership.
The container holds references to objects that exist independently.
Destroying the container does not destroy the contained objects.

KEY PHRASE: "lifecycle independence"

THE CONSTRUCTOR TEST
--------------------
Aggregation:   constructor(name, teacher) { this.teacher = teacher; }
                ^--- object passed IN from outside
Composition:   constructor(name) { this.teacher = new Teacher(name); }
                ^--- object created INSIDE — that is composition, not aggregation

REAL-WORLD PAIRS
----------------
Container           Part           Notes
Department          Teacher        Teachers survive dept dissolution
Playlist            Song           Songs exist outside playlists
Garage              Car            Cars exist outside garages
Team                Employee       Employees survive team disbanding
University          Department     Departments can be restructured
Shopping Cart       Product        Products exist in catalog independently

NOT AGGREGATION (These are Composition)
-----------------------------------------
House               Room           Rooms don't exist outside the house
Order               OrderItem      Items are specific to one order
Human               Heart          Heart created and destroyed with human
Car                 Engine (if     Engine built into car, not reusable
                    created inside)

STRUCTURAL SIGNALS IN JAVASCRIPT
----------------------------------
// Aggregation — dependency comes from OUTSIDE
class Department {
  constructor(name) {
    this.name = name;
    this.teachers = [];
  }
  addTeacher(teacher) {      // <- receives existing Teacher
    this.teachers.push(teacher);
  }
}

// Composition — dependency created INSIDE
class House {
  constructor(address, roomCount) {
    this.address = address;
    this.rooms = [];
    for (let i = 0; i < roomCount; i++) {
      this.rooms.push(new Room(i + 1)); // <- creates Room internally
    }
  }
}

UML DIAMONDS
------------
Aggregation:   Department <>---- Teacher    (open/hollow diamond)
Composition:   House ◆---- Room             (filled/solid diamond)

SHARED REFERENCE RULE
----------------------
Aggregation:   same object CAN belong to multiple containers
Composition:   object belongs to EXACTLY ONE container

LIFECYCLE RULE
--------------
Aggregation:   container destroyed -> references gone, parts SURVIVE
Composition:   container destroyed -> parts DESTROYED with it

COMPARISON SUMMARY
------------------
                    Aggregation         Composition
Ownership:          Weak                Strong
Lifecycle:          Independent         Dependent
Creation:           External (passed)   Internal (new inside constructor)
Sharing:            Allowed             Not allowed
UML diamond:        Open                Filled
==========================================================================

Aggregation visual 3


Previous: Lesson 6.1 -> Next: Lesson 6.3 ->


This is Lesson 6.2 of the OOP Interview Prep Course — 8 chapters, 41 lessons.

On this page