Loading learning content...
In the previous page, we established that interfaces are behavioral contracts—promises about what an object can do. Now we encounter one of the most liberating features of interfaces: a single class can implement multiple interfaces simultaneously.
This is profoundly different from class inheritance. Most languages restrict class inheritance to a single parent (to avoid the infamous "diamond problem"), but interface implementation has no such restriction. An object can sign as many behavioral contracts as it needs.
Think of it this way: a person can simultaneously be a driver (can drive vehicles), a swimmer (can swim), a cook (can prepare food), and a musician (can play instruments). These aren't conflicting identities—they're different capabilities the same entity possesses. In OOP terms, a class can implement Driver, Swimmer, Cook, and Musician interfaces all at once.
By the end of this page, you will understand how to implement multiple interfaces in a single class, why this capability is fundamental to flexible design, the composition patterns it enables, and the differences between multiple interface implementation and multiple inheritance.
Implementing multiple interfaces is syntactically straightforward. A class simply lists all the interfaces it promises to fulfill, then provides implementations for every method from every interface.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
// Define separate, focused interfacesinterface Printable { print(): string; getFormat(): string;} interface Saveable { save(path: string): Promise<void>; getLastSaved(): Date | null;} interface Shareable { share(recipient: string): Promise<boolean>; getShareLink(): string;} interface Exportable { export(format: 'pdf' | 'docx' | 'html'): Promise<Blob>; getSupportedFormats(): string[];} // A Document class implements ALL of these interfacesclass Document implements Printable, Saveable, Shareable, Exportable { private content: string; private lastSavedAt: Date | null = null; private id: string; constructor(content: string) { this.content = content; this.id = crypto.randomUUID(); } // === Printable implementation === print(): string { return this.content; } getFormat(): string { return 'plain-text'; } // === Saveable implementation === async save(path: string): Promise<void> { // Actual save logic here console.log(`Saving to ${path}`); this.lastSavedAt = new Date(); } getLastSaved(): Date | null { return this.lastSavedAt; } // === Shareable implementation === async share(recipient: string): Promise<boolean> { console.log(`Sharing with ${recipient}`); return true; } getShareLink(): string { return `https://docs.app/share/${this.id}`; } // === Exportable implementation === async export(format: 'pdf' | 'docx' | 'html'): Promise<Blob> { console.log(`Exporting as ${format}`); return new Blob([this.content], { type: 'text/plain' }); } getSupportedFormats(): string[] { return ['pdf', 'docx', 'html']; }} // The Document can now be used polymorphically through ANY of its interfacesconst doc = new Document("Hello, World!"); // As Printablefunction printDocument(printable: Printable): void { console.log(printable.print());} // As Saveablefunction autoSave(saveable: Saveable): void { saveable.save('/auto-save');} // As Shareablefunction shareWithTeam(shareable: Shareable): void { shareable.share('team@company.com');} // As Exportablefunction exportToPdf(exportable: Exportable): void { exportable.export('pdf');} // All work with the same Document instance!printDocument(doc);autoSave(doc);shareWithTeam(doc);exportToPdf(doc);The key insight: The same Document object can be viewed through multiple lenses. When passed to printDocument(), it's treated as a Printable. When passed to autoSave(), it's treated as a Saveable. The object has a single implementation but multiple interfaces through which it can be accessed.
This is fundamentally different from inheritance. The Document doesn't inherit from Printable—it isn't a specialized version of Printable. Instead, it fulfills the contract that Printable defines. It's a subtle but crucial distinction.
Multiple interface implementation isn't just a syntactic convenience—it fundamentally changes how we design systems. It enables several powerful capabilities:
Printable objects; it doesn't need to know about Shareable capabilities.Printable & Shareable.implements Printable, Saveable, Shareable is self-documenting.Multiple interfaces encourage thinking about objects in terms of what they can DO rather than what they ARE. A Document isn't in a category—it has capabilities. This shift from categorical to capability-based thinking leads to more flexible, composable designs.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
// Different subsystems see different views of the same objects interface Printable { print(): string;} interface Shareable { share(recipient: string): Promise<void>;} interface Deletable { delete(): Promise<void>;} // The Printing subsystem only knows about Printableclass PrintQueue { private queue: Printable[] = []; addToQueue(item: Printable): void { // Only sees print capability - doesn't know about share/delete this.queue.push(item); } processQueue(): void { this.queue.forEach(item => console.log(item.print())); }} // The Sharing subsystem only knows about Shareable class SharingService { async shareWithAll(items: Shareable[], recipients: string[]): Promise<void> { // Only sees share capability - doesn't know about print/delete for (const item of items) { for (const recipient of recipients) { await item.share(recipient); } } }} // The Cleanup subsystem only knows about Deletableclass CleanupService { async cleanup(items: Deletable[]): Promise<void> { // Only sees delete capability - doesn't know about print/share for (const item of items) { await item.delete(); } }} // A Document implementing all three can be used by all subsystemsclass Document implements Printable, Shareable, Deletable { constructor(private content: string) {} print(): string { return this.content; } async share(recipient: string): Promise<void> { /* ... */ } async delete(): Promise<void> { /* ... */ }} // Same document, different perspectivesconst doc = new Document("Report Q4"); const printQueue = new PrintQueue();const sharingService = new SharingService();const cleanupService = new CleanupService(); // Each service only sees what it needsprintQueue.addToQueue(doc); // Sees PrintablesharingService.shareWithAll([doc], ["team@co.com"]); // Sees ShareablecleanupService.cleanup([doc]); // Sees DeletableSometimes you need to accept objects that implement multiple specific interfaces. TypeScript provides intersection types for this purpose using the & operator. This lets you specify exactly which combination of capabilities you require.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
interface Identifiable { getId(): string; getName(): string;} interface Timestamped { getCreatedAt(): Date; getUpdatedAt(): Date;} interface Auditable { getLastModifiedBy(): string; getChangeHistory(): ChangeEntry[];} interface Versionable { getVersion(): number; getPreviousVersions(): string[];} // Using intersection types to require MULTIPLE interfaces// This function needs objects that are:// - Identifiable (has ID and name)// - Timestamped (has creation/update times)// - Auditable (tracks who changed what)function generateAuditReport( entity: Identifiable & Timestamped & Auditable): AuditReport { return { entityId: entity.getId(), entityName: entity.getName(), createdAt: entity.getCreatedAt(), updatedAt: entity.getUpdatedAt(), lastModifiedBy: entity.getLastModifiedBy(), history: entity.getChangeHistory() };} // This backup function requires Identifiable + Versionablefunction backupEntity( entity: Identifiable & Versionable): void { console.log(`Backing up ${entity.getName()} v${entity.getVersion()}`); // Store current and previous versions} // A class implementing all four can be used with any combinationclass CriticalDocument implements Identifiable, Timestamped, Auditable, Versionable { private id: string; private name: string; private createdAt: Date; private updatedAt: Date; private modifiedBy: string; private history: ChangeEntry[] = []; private version: number = 1; private previousVersions: string[] = []; constructor(name: string, createdBy: string) { this.id = crypto.randomUUID(); this.name = name; this.createdAt = new Date(); this.updatedAt = new Date(); this.modifiedBy = createdBy; } // Identifiable getId(): string { return this.id; } getName(): string { return this.name; } // Timestamped getCreatedAt(): Date { return this.createdAt; } getUpdatedAt(): Date { return this.updatedAt; } // Auditable getLastModifiedBy(): string { return this.modifiedBy; } getChangeHistory(): ChangeEntry[] { return [...this.history]; } // Versionable getVersion(): number { return this.version; } getPreviousVersions(): string[] { return [...this.previousVersions]; }} // CriticalDocument satisfies both function requirementsconst doc = new CriticalDocument("Financial Report", "alice@company.com"); generateAuditReport(doc); // ✅ Works - doc is Identifiable & Timestamped & AuditablebackupEntity(doc); // ✅ Works - doc is Identifiable & Versionable // A simpler class implementing only some interfacesclass SimpleNote implements Identifiable, Timestamped { constructor( private id: string, private name: string, private createdAt: Date, private updatedAt: Date ) {} getId(): string { return this.id; } getName(): string { return this.name; } getCreatedAt(): Date { return this.createdAt; } getUpdatedAt(): Date { return this.updatedAt; }} const note = new SimpleNote("id-123", "Quick Note", new Date(), new Date()); // generateAuditReport(note); // ❌ Error! SimpleNote is not Auditable// backupEntity(note); // ❌ Error! SimpleNote is not VersionableIntersection types let you express precisely what combination of capabilities you need. Rather than creating a new interface that combines all the methods (which would require a new type for every combination), you compose existing interfaces on demand. This keeps your interface definitions clean and focused.
Many languages restrict class inheritance to a single parent to avoid the "diamond problem." But they allow multiple interface implementation without restriction. Why the difference?
The answer lies in what each provides:
Classes provide implementation — They contain actual code that runs. With multiple inheritance, if two parent classes implement the same method differently, the child faces an irresolvable conflict: which implementation should it use?
Interfaces provide only contracts — They declare what methods must exist but contain no implementation. There's no conflict because there's no code to conflict with.
| Aspect | Multiple Class Inheritance | Multiple Interface Implementation |
|---|---|---|
| Provides | Implementation (code) | Contract (method signatures only) |
| Diamond Problem | Yes - conflicting implementations | No - no implementations to conflict |
| State Inheritance | Inherits parent state (fields) | No state inheritance |
| Allowed in Most Languages | No (Java, C#, TypeScript) | Yes |
| Cognitive Complexity | High - must understand all parent implementations | Lower - just implement declared methods |
| Fragile Base Class Problem | Yes - parent changes affect children | Minimal - only contract changes affect implementers |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
// The Diamond Problem with Multiple Inheritance (hypothetical)// If TypeScript allowed multiple class inheritance: class Scannable { scan(): Image { console.log("Scanning using default scanner..."); return new Image(); }} class Faxable { scan(): Image { // Same method name, different implementation! console.log("Scanning for fax transmission..."); return new Image(); }} // ❌ PROBLEM: If MultiFunctionDevice extended both,// which scan() implementation would it inherit?// class MultiFunctionDevice extends Scannable, Faxable { } // With interfaces, there's no ambiguity: interface IScanner { scan(): Image;} interface IFax { scan(): Image; // Same signature - no problem! send(number: string, image: Image): void;} // The CLASS decides the implementation - there's only ONEclass MultiFunctionDevice implements IScanner, IFax { scan(): Image { // We provide THE implementation - no inheritance conflict console.log("Scanning via multi-function device..."); return new Image(); } send(number: string, image: Image): void { console.log(`Faxing to ${number}...`); }} // Both interface contracts are satisfied by the SAME method implementation// No ambiguity, no diamond problemWhile interfaces avoid the diamond problem, you can still encounter issues if two interfaces declare methods with the same name but DIFFERENT signatures. For instance, if one interface declares process(): void and another declares process(): Result, implementing both creates a conflict. Design interfaces carefully to avoid such naming collisions.
A powerful pattern enabled by multiple interfaces is the mixin pattern. Mixins provide reusable functionality that can be "mixed into" classes. When combined with interfaces, mixins offer a way to share implementation while maintaining type safety.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
// Define interfaces for each capabilityinterface Timestampable { createdAt: Date; updatedAt: Date; touch(): void;} interface SoftDeletable { deletedAt: Date | null; isDeleted(): boolean; softDelete(): void; restore(): void;} // Type for a class constructortype Constructor<T = {}> = new (...args: any[]) => T; // Mixin functions that add implementation to any base classfunction Timestampable<TBase extends Constructor>(Base: TBase) { return class extends Base implements Timestampable { createdAt = new Date(); updatedAt = new Date(); touch(): void { this.updatedAt = new Date(); } };} function SoftDeletable<TBase extends Constructor>(Base: TBase) { return class extends Base implements SoftDeletable { deletedAt: Date | null = null; isDeleted(): boolean { return this.deletedAt !== null; } softDelete(): void { this.deletedAt = new Date(); } restore(): void { this.deletedAt = null; } };} // Base entity classclass BaseEntity { id: string = crypto.randomUUID();} // Compose mixins to create a class with multiple behaviorsconst TimestampedEntity = Timestampable(BaseEntity);const FullEntity = SoftDeletable(TimestampedEntity); // Or apply both at onceclass User extends SoftDeletable(Timestampable(BaseEntity)) { constructor(public name: string, public email: string) { super(); }} // User now has all capabilities from the interfacesconst user = new User("Alice", "alice@example.com"); console.log(user.id); // From BaseEntityconsole.log(user.createdAt); // From Timestampableconsole.log(user.isDeleted()); // From SoftDeletable (false) user.touch(); // Update timestampuser.softDelete(); // Soft deleteconsole.log(user.isDeleted()); // trueuser.restore(); // Restoreconsole.log(user.isDeleted()); // false // Type system knows about all capabilitiesfunction processEntity(entity: Timestampable & SoftDeletable): void { entity.touch(); if (entity.isDeleted()) { console.log("Entity was deleted at:", entity.deletedAt); }} processEntity(user); // ✅ Works - User implements both interfacesThe mixin pattern demonstrates how multiple interfaces enable horizontal reuse—sharing functionality across unrelated class hierarchies. Unlike vertical inheritance (where child gets everything from parent), mixins let you selectively compose exactly the capabilities you need.
This pattern is particularly valuable for:
Let's examine how multiple interfaces are used in production systems to create flexible, well-structured code.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
// ========================================// PATTERN 1: Entity Lifecycle Interfaces// ======================================== interface Identifiable { readonly id: string;} interface Persistable { save(): Promise<void>; refresh(): Promise<void>;} interface Validatable { validate(): ValidationResult; isValid(): boolean;} interface Serializable { toJSON(): object; fromJSON(data: object): void;} // Domain entities implement all persistence-related interfacesclass Order implements Identifiable, Persistable, Validatable, Serializable { readonly id: string; private items: OrderItem[] = []; private status: OrderStatus = 'draft'; constructor(id?: string) { this.id = id ?? crypto.randomUUID(); } // Persistable async save(): Promise<void> { /* Save to database */ } async refresh(): Promise<void> { /* Reload from database */ } // Validatable validate(): ValidationResult { const errors: string[] = []; if (this.items.length === 0) errors.push("Order must have items"); if (this.getTotal() <= 0) errors.push("Order total must be positive"); return { valid: errors.length === 0, errors }; } isValid(): boolean { return this.validate().valid; } // Serializable toJSON(): object { return { id: this.id, items: this.items, status: this.status }; } fromJSON(data: object): void { /* Populate from JSON */ } // Order-specific methods addItem(item: OrderItem): void { this.items.push(item); } getTotal(): number { return this.items.reduce((sum, i) => sum + i.price, 0); }} // ========================================// PATTERN 2: Repository with Multiple Query Interfaces// ======================================== interface Readable<T, ID> { findById(id: ID): Promise<T | null>; findAll(): Promise<T[]>;} interface Writable<T, ID> { save(entity: T): Promise<T>; update(id: ID, entity: Partial<T>): Promise<T>; delete(id: ID): Promise<boolean>;} interface Searchable<T> { search(query: SearchQuery): Promise<T[]>; count(query: SearchQuery): Promise<number>;} interface Pageable<T> { findPage(page: number, size: number): Promise<Page<T>>;} // Full repository implements all query patternsclass UserRepository implements Readable<User, string>, Writable<User, string>, Searchable<User>, Pageable<User> { // Readable async findById(id: string): Promise<User | null> { /* ... */ } async findAll(): Promise<User[]> { /* ... */ } // Writable async save(entity: User): Promise<User> { /* ... */ } async update(id: string, entity: Partial<User>): Promise<User> { /* ... */ } async delete(id: string): Promise<boolean> { /* ... */ } // Searchable async search(query: SearchQuery): Promise<User[]> { /* ... */ } async count(query: SearchQuery): Promise<number> { /* ... */ } // Pageable async findPage(page: number, size: number): Promise<Page<User>> { /* ... */ }} // Different parts of the app can depend on just what they needclass UserLookupService { constructor(private repo: Readable<User, string>) {} async getUser(id: string): Promise<User | null> { return this.repo.findById(id); }} class AdminUserService { constructor(private repo: Readable<User, string> & Writable<User, string>) {} async createUser(data: UserData): Promise<User> { const user = new User(data); return this.repo.save(user); }} // ========================================// PATTERN 3: Event System with Multiple Concerns// ======================================== interface EventHandler<T> { handle(event: T): Promise<void>;} interface Retryable { maxRetries: number; retryDelay: number;} interface Loggable { logLevel: 'debug' | 'info' | 'warn' | 'error'; logExecution: boolean;} interface Measurable { recordMetrics: boolean; getMetrics(): HandlerMetrics;} // Critical event handlers implement all observability interfacesclass OrderCreatedHandler implements EventHandler<OrderCreatedEvent>, Retryable, Loggable, Measurable { // Retryable maxRetries = 3; retryDelay = 1000; // Loggable logLevel: 'debug' | 'info' | 'warn' | 'error' = 'info'; logExecution = true; // Measurable recordMetrics = true; private executionCount = 0; private totalDuration = 0; // EventHandler async handle(event: OrderCreatedEvent): Promise<void> { // Process order created event } getMetrics(): HandlerMetrics { return { executionCount: this.executionCount, averageDuration: this.totalDuration / this.executionCount }; }}While multiple interface implementation is powerful, it requires thoughtful design to avoid complexity and maintain clarity.
-able suffixes (Serializable, Comparable, Observable) to signal behavioral contracts rather than IS-A relationships.A & B to require multiple interfaces rather than creating a new combined interface.A class implementing many interfaces might be a sign of the "God Object" anti-pattern—a class that knows too much and does too much. If a class implements more than 5-7 interfaces, critically examine whether it should be split into smaller, more focused classes.
We've explored how a single class can implement multiple interfaces simultaneously, enabling rich polymorphic behavior and flexible design.
A & B to accept only objects implementing both interfaces.What's Next:
Now that we understand how to implement multiple interfaces, we'll see interface polymorphism in action through comprehensive examples. We'll explore how interface-based polymorphism enables patterns like the Strategy pattern, enables dependency injection, and creates the foundation for plugin architectures.
You now understand how to implement multiple interfaces and why this capability is fundamental to flexible object-oriented design. This knowledge enables composition-based designs that avoid the rigidity of deep inheritance hierarchies.