JavaScript · Classes & OOP

JavaScript Mixins & Composition — Sharing Behavior Beyond Single Inheritance

6 min read Updated 2026-06-18

Practice Mixins & Composition interview questions

The limits of single inheritance

JavaScript classes support only single inheritance: a class can extend exactly one parent. That's fine for strict "is-a" hierarchies, but real software often needs to share capabilities across unrelated types. A Robot and a Car might both need "serializable," a User and an Order might both need "timestamped" — yet they don't share a common ancestor. Forcing them into one inheritance tree leads to the infamous deep, brittle hierarchies and "god base classes." Mixins and composition solve this by letting you combine independent behaviors without a single rigid lineage.

The guiding principle, often summarized as "favor composition over inheritance," is that building objects from small, focused pieces is more flexible than carving an ever-deeper class tree.

What is a mixin?

A mixin is a reusable bundle of methods that you mix into a class or object, independent of the inheritance chain. The simplest form copies methods onto a prototype with Object.assign.

const serializable = {
  toJSON() { return JSON.stringify(this) },
  fromJSON(s) { return Object.assign(this, JSON.parse(s)) }
}

class User {
  constructor(name) { this.name = name }
}
Object.assign(User.prototype, serializable)   // mix in the behavior

new User('Ada').toJSON()   // '{"name":"Ada"}'

Now any User instance has toJSON, even though User doesn't extend anything. The same serializable object can be mixed into completely unrelated classes.

Object-level mixins

You can also mix capabilities directly into a single object rather than a whole class — handy for one-off composition.

const comparable = {
  equals(other) { return this.id === other.id }
}
const loggable = {
  log() { console.log(`[${this.constructor.name}] ${this.id}`) }
}

const entity = Object.assign({ id: 1 }, comparable, loggable)
entity.equals({ id: 1 })   // true

Each source's methods are copied in; later sources override earlier ones on key conflicts — the same "last wins" rule as object spread.

Functional mixins (class factories)

The most powerful mixin pattern is a function that takes a base class and returns a new subclass extending it. This composes cleanly and supports super.

const Timestamped = (Base) => class extends Base {
  constructor(...args) {
    super(...args)
    this.createdAt = Date.now()
  }
  touch() { this.updatedAt = Date.now() }
}

const Serializable = (Base) => class extends Base {
  serialize() { return JSON.stringify(this) }
}

class Model {}
class Document extends Serializable(Timestamped(Model)) {}   // stack mixins

const doc = new Document()
doc.createdAt    // a timestamp
doc.serialize()  // works too

This "class factory" approach is more robust than Object.assign because each mixin becomes a real link in the prototype chain — so super works, instanceof-style checks behave, and ordering is explicit. Libraries and frameworks favor this pattern.

Why mixins beat deep hierarchies

Consider modeling abilities like flying, swimming, and walking across animals. With single inheritance you'd contort the tree (FlyingSwimmingAnimal?). With mixins each ability is an independent piece you compose as needed.

const CanFly  = (B) => class extends B { fly()  { return 'flying'  } }
const CanSwim = (B) => class extends B { swim() { return 'swimming'} }

class Animal {}
class Duck extends CanFly(CanSwim(Animal)) {}   // both abilities, no awkward tree
class Fish extends CanSwim(Animal) {}            // just one

new Duck().fly()    // 'flying'
new Duck().swim()   // 'swimming'

Each capability is defined once and combined à la carte. Adding a new ability doesn't require reshaping a hierarchy — you just write another mixin.

Composition with plain objects (delegation)

Beyond mixins, pure composition builds an object's behavior by holding other objects and delegating to them, rather than inheriting at all.

function createPlayer(name) {
  const health = createHealth(100)     // composed pieces
  const inventory = createInventory()
  return {
    name,
    takeDamage: health.decrease,        // delegate
    addItem: inventory.add,
  }
}

This "has-a" style keeps each piece independently testable and swappable. It avoids the prototype chain entirely and is often the most flexible approach for complex domain objects.

Pitfalls of mixins

Mixins are powerful but have sharp edges to respect:

// name collisions — a later mixin silently overwrites an earlier method
Object.assign(proto, mixinA, mixinB)   // if both define save(), B wins silently
  • Name collisions: two mixins defining the same method clobber each other quietly. Keep mixin method names specific, or detect collisions in development.
  • Hidden dependencies: a mixin that assumes this.id or this.save() exists couples it to its host invisibly. Document what each mixin requires.
  • Order sensitivity: with class-factory mixins, the stacking order affects super chains. Be deliberate about sequence.

These aren't reasons to avoid mixins — just to use focused, well-named, self-contained ones.

Choosing your tool

A quick decision guide:

  • extends — a genuine, stable "is-a" relationship with one clear parent.
  • Mixins — orthogonal capabilities shared across unrelated classes (serializable, observable, timestamped).
  • Composition/delegation — complex objects assembled from independent, swappable parts; when you want maximum flexibility and testability.

Most well-designed systems use all three: shallow inheritance for the core type, mixins for cross-cutting traits, and composition for assembling behavior. Reaching first for deep inheritance is the usual mistake.

Key takeaways

  • JavaScript allows only single inheritance; mixins and composition share behavior across unrelated types.
  • A mixin is a reusable method bundle, applied via Object.assign (flat copy) or as a class factory (Base) => class extends Base {...} (real prototype link, super-aware).
  • Class-factory mixins stack cleanly to compose multiple independent capabilities.
  • Pure composition (has-a, delegation) often beats inheritance for complex, evolving objects.
  • Watch for mixin pitfalls: name collisions, hidden this dependencies, and order sensitivity.
  • Favor composition over deep inheritance; combine extends, mixins, and composition by purpose.

Mastering these patterns frees you from forcing every relationship into a single class tree — the hallmark of flexible, maintainable object-oriented JavaScript.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.