TypeScript Mixins
last modified October 15, 2026
A class in TypeScript can extend only one other class. Mixins work around this limit: a mixin is a function that takes a class and returns a new class that extends it with extra properties or methods. Several mixins can be applied one after another, so a class can be composed from independent, reusable parts, such as timestamps, logging, or serialization. TypeScript infers the type of the resulting class, so all added members are fully typed.
Basic Mixin
A mixin is a generic function whose type parameter is constrained to constructor types.
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
};
}
class User {
constructor(public name: string) {}
}
const TimestampedUser = Timestamped(User);
const user = new TimestampedUser("Alice");
console.log(user.name);
console.log(user.createdAt instanceof Date);
console.log(user instanceof User);
The Constructor type describes anything that can be
called with new. Its any[] parameter list is
required by TypeScript for mixin classes, so that the returned class can
forward any constructor arguments to the base class.
Timestamped returns an anonymous class that extends
Base and adds a createdAt property. The new class
keeps the constructor of User, so it is created with a name. The
instance has both properties, and it is still an instance of
User.
$ npx tsx basic_mixin.ts Alice true true
Combining Mixins
Mixins are combined by nesting the calls. Each one adds its members to the result of the previous one.
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date(2026, 9, 15);
};
}
function Loggable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
log(message: string): void {
console.log(`[${this.constructor.name}] ${message}`);
}
};
}
class User {
constructor(public name: string) {}
}
class SmartUser extends Loggable(Timestamped(User)) {}
const user = new SmartUser("Bob");
user.log(`created ${user.createdAt.toDateString()}`);
user.log(`name is ${user.name}`);
Loggable(Timestamped(User)) produces a class with
name, createdAt, and log. Extending
the result with a named class, SmartUser, gives the composed
class a proper name, which appears in the log messages through
this.constructor.name. The fixed date makes the output
reproducible.
$ npx tsx multiple_mixins.ts [SmartUser] created Thu Oct 15 2026 [SmartUser] name is Bob
Mixins with Methods
A mixin can add methods that work with the members it adds and with
this.
type Constructor<T = {}> = new (...args: any[]) => T;
function Aged<TBase extends Constructor>(Base: TBase) {
return class extends Base {
birthYear = 1990;
ageIn(year: number): number {
return year - this.birthYear;
}
isAdultIn(year: number): boolean {
return this.ageIn(year) >= 18;
}
};
}
class User {
constructor(public name: string) {}
}
const AgedUser = Aged(User);
const user = new AgedUser("Charlie");
user.birthYear = 2010;
console.log(`${user.name} is ${user.ageIn(2026)}`);
console.log(user.isAdultIn(2026), user.isAdultIn(2030));
The methods of the mixin call each other through this,
like methods of an ordinary class. The year is passed as an argument instead
of reading the current date, which keeps the methods predictable and easy to
test.
$ npx tsx mixin_with_methods.ts Charlie is 16 false true
Mixins with Properties
Mixins can add properties with default values, together with the methods that manage them.
type Constructor<T = {}> = new (...args: any[]) => T;
type Role = "user" | "editor" | "admin";
function RoleBased<TBase extends Constructor>(Base: TBase) {
return class extends Base {
role: Role = "user";
promote(role: Role): this {
this.role = role;
return this;
}
can(action: "read" | "write" | "delete"): boolean {
const allowed: Record<Role, string[]> = {
user: ["read"],
editor: ["read", "write"],
admin: ["read", "write", "delete"],
};
return allowed[this.role].includes(action);
}
};
}
class User {
constructor(public name: string) {}
}
const user = new (RoleBased(User))("Diana");
console.log(user.role, user.can("write"));
user.promote("editor");
console.log(user.role, user.can("write"), user.can("delete"));
The role property starts as "user". The
promote method returns this, so its return type is
the full composed type, which allows method chaining. The Role
union makes sure that only valid roles can be assigned.
$ npx tsx mixin_with_properties.ts user false editor true false
Overriding Methods
A mixin can override a method of the base class and call the
original through super.
type Constructor<T = {}> = new (...args: any[]) => T;
function Polite<TBase extends Constructor<{ greet(): string }>>(Base: TBase) {
return class extends Base {
override greet(): string {
return `${super.greet()} Nice to meet you!`;
}
};
}
class User {
constructor(public name: string) {}
greet(): string {
return `Hi, I'm ${this.name}.`;
}
}
const PoliteUser = Polite(User);
console.log(new User("Eve").greet());
console.log(new PoliteUser("Eve").greet());
The constraint Constructor<{ greet(): string }>
tells the compiler that the base class has a greet method, so
the mixin can override it and call super.greet(). The original
class is not changed; only instances of PoliteUser use the
extended greeting.
$ npx tsx mixin_with_overrides.ts Hi, I'm Eve. Hi, I'm Eve. Nice to meet you!
Constrained Mixins
A constraint also restricts which classes a mixin can be applied to.
type Constructor<T = {}> = new (...args: any[]) => T;
function Activatable<TBase extends Constructor<{ id: number }>>(Base: TBase) {
return class extends Base {
active = false;
activate(): string {
this.active = true;
return `entity ${this.id} activated`;
}
};
}
class Account {
constructor(public id: number, public owner: string) {}
}
const ActivatableAccount = Activatable(Account);
const account = new ActivatableAccount(7, "Frank");
console.log(account.activate(), account.active);
The mixin needs an id property, and its constraint says
so. Account has one, so the mixin can be applied.
$ npx tsx constrained_mixin.ts entity 7 activated true
type Constructor<T = {}> = new (...args: any[]) => T;
function Activatable<TBase extends Constructor<{ id: number }>>(Base: TBase) {
return class extends Base {
active = false;
};
}
class Note {
constructor(public text: string) {}
}
const ActivatableNote = Activatable(Note);
A Note has no id, so the compiler rejects
the call. Without the constraint, the mixin could not use
this.id at all.
$ npx tsc --noEmit
constrained_error.ts(13,37): error TS2345: Argument of type 'typeof Note' is not assignable to parameter of type 'Constructor<{ id: number; }>'.
Types of construct signatures are incompatible.
Type 'new (text: string) => Note' is not assignable to type 'new (...args: any[]) => { id: number; }'.
Property 'id' is missing in type 'Note' but required in type '{ id: number; }'.
Typing Mixin Instances
The class returned by a mixin is anonymous. To use its instance type in annotations, it is derived with utility types.
type Constructor<T = {}> = new (...args: any[]) => T;
function Taggable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
tags: string[] = [];
tag(...tags: string[]): void {
this.tags.push(...tags);
}
};
}
class Article {
constructor(public title: string) {}
}
const TaggableArticle = Taggable(Article);
type TaggableArticle = InstanceType<typeof TaggableArticle>;
function describe(article: TaggableArticle): string {
return `${article.title} [${article.tags.join(", ")}]`;
}
const post = new TaggableArticle("Mixins in TypeScript");
post.tag("typescript", "oop");
console.log(describe(post));
typeof TaggableArticle is the type of the class, and
InstanceType turns it into the type of its instances. Giving the
type the same name as the constant is a common pattern; TypeScript keeps
values and types in separate namespaces. The describe function
can now require exactly this composed type.
$ npx tsx mixin_types.ts Mixins in TypeScript [typescript, oop]
The applyMixins Pattern
An alternative pattern copies methods from several classes onto a target class at runtime, and uses interface merging to describe the result.
class Jumpable {
jump(): string {
return "jumping";
}
}
class Duckable {
duck(): string {
return "ducking";
}
}
class Sprite {
constructor(public name: string) {}
}
interface Sprite extends Jumpable, Duckable {}
function applyMixins(target: Function, sources: Function[]): void {
for (const source of sources) {
for (const name of Object.getOwnPropertyNames(source.prototype)) {
if (name !== "constructor") {
Object.defineProperty(
target.prototype,
name,
Object.getOwnPropertyDescriptor(source.prototype, name) ?? {},
);
}
}
}
}
applyMixins(Sprite, [Jumpable, Duckable]);
const player = new Sprite("player");
console.log(player.name, player.jump(), player.duck());
The interface declaration with the same name as the class merges
with it, so the compiler knows that Sprite has
jump and duck. applyMixins then copies
the methods to the prototype. This pattern does not check that the copying
really happens and does not support properties initialized in constructors,
so the class-returning mixins shown above are usually preferable.
$ npx tsx apply_mixins.ts player jumping ducking
Best Practices
- Keep Mixins Small: Each mixin should add one capability, such as timestamps or tagging.
- Declare Requirements: Use constraints like
Constructor<{ id: number }>when a mixin depends on members of the base class. - Name the Composed Class: Extend the mixin result with a named class for readable names in logs and errors.
- Derive Instance Types: Use
InstanceType<typeof X>to annotate variables and parameters with a composed type. - Consider Composition: Plain objects or functions that receive collaborators are often simpler than deep mixin chains.
Source
This tutorial covered mixins in TypeScript. They build classes from small, reusable pieces while keeping full type information.
Author
List all TypeScript tutorials.