Mixins & Composition
The problem with deep inheritance
Section titled “The problem with deep inheritance”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 class factory
Section titled “A mixin is a class factory”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 Timestampedu.activate(); // from Activatableu.name; // from UserThe 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"]
Constrained mixins
Section titled “Constrained mixins”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.
Composition over inheritance
Section titled “Composition over inheritance”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.