Skip to content

Mixins & Composition

Single inheritance forces every capability into one rigid chain. If Timestamped, Serializable, and Comparable are all behaviors you want to share across unrelated classes, a linear hierarchy cannot express that — you would have to pick one parent and duplicate the rest.

Mixins solve this by making a behavior a function that adds features to any base class, so a class can compose several.

A mixin is a function that takes a base class (a constructor) and returns a new class that extends it with extra behavior.

type Constructor = new (...args: any[]) => {};
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
};
}
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActive = false;
activate() { this.isActive = true; }
};
}
class User {
constructor(public name: string) {}
}
// Compose several behaviors onto User:
const SmartUser = Activatable(Timestamped(User));
const u = new SmartUser("Ada");
u.createdAt; // from Timestamped
u.activate(); // from Activatable
u.name; // from User

The generic TBase extends Constructor constraint is what makes this type-safe: it says “whatever class you pass in, I return a class that has all of its members plus the new ones.” TypeScript tracks the accumulated shape through each wrap.

flowchart LR
  base["class User"] --> m1["Timestamped(...)
+ createdAt"]
  m1 --> m2["Activatable(...)
+ activate()"]
  m2 --> result["SmartUser
has all three"]
Inheritance is a chain; mixins compose

Sometimes a mixin needs the base class to already have certain members. Constrain the base to a constructor that produces the required shape:

type Positioned = new (...args: any[]) => { x: number; y: number };
function Movable<TBase extends Positioned>(Base: TBase) {
return class extends Base {
move(dx: number, dy: number) {
this.x += dx; // ✅ allowed — the constraint guarantees x exists
this.y += dy;
}
};
}

Now Movable can only be applied to a base whose instances have x and y, and inside it those members are known to exist.

Mixins are one form of a broader principle: prefer assembling behavior from small, independent pieces over deriving it through a rigid hierarchy. Often you do not even need mixins — plain object composition (holding collaborators as fields and delegating to them) is simpler still and avoids the class-factory machinery entirely. Reach for mixins when you genuinely need the composed result to be a class with all behaviors merged onto its instances.

What is a mixin in TypeScript?
Why do mixins use a generic constraint like `TBase extends new (...args: any[]) => {}`?
What problem do mixins solve that single inheritance cannot?
When is plain object composition preferable to a mixin?