Chapter 7.6☕ 18 min

Mixins, Composition vs Inheritance Patterns

Is-A chhodo, Has-A apnao. Mixins se jodo, Composition se todo.

01Inheritance: The Is-A Relationship

Inheritance means "Is-A" — Dog is an Animal, Car is a Vehicle. The child class extends the parent and inherits all its properties and methods.

JavaScript supports single inheritance only — a class can only extend one parent. No multiple inheritance like C++ or Python.

class Animal {
  breathe() { return "breathing"; }
}
class Dog extends Animal {
  bark() { return "woof"; }
}

const rex = new Dog();
console.log(rex.breathe()); // Inherited
console.log(rex.bark());    // Own

// The problem: What if Dog needs to swim?
// Cannot extend both Animal AND Swimmer!
// class Dog extends Animal, Swimmer {} // SyntaxError!

Problems with inheritance:

1. Fragile base class — a change in the parent can break all children silently.

2. Deep hierarchies become rigid — Animal > Mammal > Canine > Dog > Puppy. Six levels just to add a tail wag.

3. "Is-A" doesn't fit every scenario — Dog can walk AND swim. Which parent does it extend?

The Gorilla-Banana Problem (coined by Joe Armstrong): "You wanted a banana but you got a gorilla holding the banana AND the entire jungle." Inheritance forces you to take EVERYTHING from the parent — even what you don't need. You want just serialize() but you inherit 40 methods you never called.

When inheritance works well: Clear "is-a" relationships — Array is an Object, Error is a Throwable. Shallow hierarchies (1-2 levels) are fine.

02Mixins: Adding Abilities Without Inheritance

A Mixin is a way to add methods to a class without using extends. It bridges the gap of multiple inheritance — a class can receive behavior from many mixins.

Object.assign Mixin: Copy methods directly onto the prototype.

// Mixin Objects
const swimMixin = {
  swim() { return "swimming"; }
};

const fetchMixin = {
  fetch() { return "fetching"; }
};

// Apply mixins to prototype
class Dog {}
Object.assign(Dog.prototype, swimMixin, fetchMixin);

const rex = new Dog();
console.log(rex.swim());  // "swimming"
console.log(rex.fetch()); // "fetching"

Class Factory Mixin: A function that takes a base class and returns a new class extending it.

// Class Factory Mixin (cleaner pattern)
const Swimmer = (Base) => class extends Base {
  swim() { return "swimming"; }
};

const Fetcher = (Base) => class extends Base {
  fetch() { return "fetching"; }
};

class Animal {
  breathe() { return "breathing"; }
}

class Dog extends Fetcher(Swimmer(Animal)) {
  bark() { return "woof"; }
}

const buddy = new Dog();
console.log(buddy.swim());    // From Swimmer mixin
console.log(buddy.fetch());   // From Fetcher mixin
console.log(buddy.breathe()); // From Animal base
Prototype chain depth: Class factory mixins create intermediate classes. Dog → Fetcher → Swimmer → Animal. V8 must traverse a longer prototype chain for method lookups. Too many mixins can slow down property access. Prefer 2-3 mixins max, or use flat composition.

Drawbacks of mixins: (1) Method name collisions — two mixins with the same method name, last one wins silently. (2) Harder to debug — where did a method come from? (3) Implicit dependencies — mixins may expect certain properties on the target.

03Composition: The Has-A Relationship

Composition flips the paradigm: instead of "Dog IS a Swimmer", think "Dog HAS the ability to swim". Build objects from smaller, focused pieces rather than inheriting from large base classes.

Delegation: Dog holds a reference to a swimmer object and delegates the swim call. The dog doesn't inherit swimming — it has a swimming capability.

// Capabilities as standalone objects
const canSwim = {
  swim() { return this.name + " swims"; }
};

const canBark = {
  bark() { return this.name + " barks"; }
};

// Compose a Dog by combining capabilities
function createDog(name) {
  const state = { name };
  
  // Combine behavior and state
  return {
    ...state,
    ...canSwim,
    ...canBark
  };
}

const rex = createDog("Rex");
console.log(rex.swim()); // "Rex swims"
console.log(rex.bark()); // "Rex barks"

Adding new capabilities is trivial — no class hierarchy changes needed:

const canFly = {
  fly() { return this.name + " flies"; }
};

function createBird(name) {
  return { ...{ name }, ...canSwim, ...canFly };
}

const eagle = createBird("Eagle");
console.log(eagle.swim()); // "Eagle swims"
console.log(eagle.fly());  // "Eagle flies"

Why composition wins: Each piece is independent, testable in isolation, and reusable. You pick only what you need — no gorilla, no jungle, just the banana. 🍌

04Functional Mixins & Closures

A Functional Mixin is a function that takes an object and adds properties/methods to it. The real power: it can use closures to add truly private state.

Unlike object mixins (which are just property copies), functional mixins can encapsulate private data that no outside code can access.

// Functional Mixin with private state
function withLogging(obj) {
  let logCount = 0; // Private to the mixin!
  
  Object.assign(obj, {
    log(message) {
      logCount++;
      console.log("[" + logCount + "] " + message);
    },
    getLogCount() {
      return logCount;
    }
  });
  
  return obj;
}

const service = { name: "AuthService" };
withLogging(service);

service.log("User logged in");
service.log("Token refreshed");
console.log(service.getLogCount()); // 2
// service.logCount -> undefined (private!)
How the closure works: The logCount variable lives in the closure scope of withLogging. The log and getLogCount methods can read/write it, but no external code can access service.logCount directly. This is true encapsulation without classes — pure JavaScript closure magic.

When to use functional mixins: When you need private state mixed into existing objects, when you want to add logging/caching/validation to any object without modifying its class.

05When to Use What: Decision Matrix

Not every problem needs the same tool. Here's your decision matrix:

// Decision Matrix
// IS-A?    → Inheritance (Array extends Object)
// CAN-DO?  → Mixin (canSerialize, canValidate)
// HAS-A?   → Composition (Car has Engine)
// CHANGES? → Composition (swap behavior at runtime)

Use Inheritance when:

• Clear "is-a" relationship (Dog is an Animal)

• Single base class, shallow hierarchy (1-2 levels max)

• Child truly is a specialized version of the parent

Use Mixins when:

• Need to share behavior across unrelated classes

• Adding capabilities (Serializable, Loggable, Validatable)

• Cannot change the class hierarchy but need to extend it

Use Composition when:

• Behavior changes at runtime (swap strategies)

• Need many small, independent pieces

• Want to avoid deep, rigid hierarchies

📋 Real-world trend: Modern frontend frameworks moved from inheritance to composition. React: class components → hooks. Vue: mixins → composables. Angular: base classes → services. The Gang of Four said it decades ago: "Favor composition over inheritance." JavaScript makes this natural — functions are first-class citizens.

Lo kar liya — Key Points:

  • ✅ Inheritance is "is-a" (Dog is Animal); JS supports single inheritance only
  • ✅ Mixins add behavior to classes without inheritance via Object.assign or class factories
  • ✅ Composition is "has-a" (Dog has the ability to swim); build objects from small pieces
  • ✅ Functional mixins can encapsulate private state using closures
  • ✅ Favor composition over inheritance — more flexible, testable, avoids deep hierarchies
  • ✅ Modern frameworks (React hooks, Vue composables) use composition, not inheritance
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase