TypeScript Inheritance — Complete Guide
In this tutorial, you will learn about TypeScript Inheritance. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript inheritance via extends lets you build class hierarchies where child classes reuse, override, and extend parent behavior, with super calling parent methods and mixins simulating multiple inheritance for complex reuse scenarios.
What You'll Learn
- The
extendskeyword for single inheritance - The
superkeyword for parent access - Method overriding and access
- Polymorphism and type-safe hierarchies
- Mixins for multiple inheritance
Why It Matters
Inheritance is a fundamental OOP concept that models "is-a" relationships — a Dog is an Animal, a SavingsAccount is a BankAccount. Properly designed inheritance hierarchies reduce code duplication and make your system's domain model explicit.
Real-World Use
The Doda Browser extension API has a hierarchy of event types: Event → TabEvent → TabCreatedEvent, each adding more specific properties. NestJS uses inheritance for its base controller and service classes, providing common CRUD operations.
Learning Path
flowchart LR A[Abstract Classes] --> B[Inheritance] B --> C[Decorators] B --> D[You Are Here] C --> E[This Typing] E --> F[Index Signatures]
Basic Inheritance
class Animal {
constructor(public name: string) {}
speak(): string {
return `${this.name} makes a sound`;
}
move(distance: number): string {
return `${this.name} moves ${distance}m`;
}
}
class Dog extends Animal {
constructor(name: string) {
super(name); // Must call super first!
}
speak(): string {
return `${this.name} barks`;
}
}
const dog = new Dog("Rex");
console.log(dog.speak()); // Rex barks
console.log(dog.move(10)); // Rex moves 10m
Think of extends as saying: "Dog IS an Animal with all its capabilities, plus its own special behavior."
Method Overriding
Subclasses can override methods and call the parent version with super:
class BaseLogger {
log(level: string, message: string): void {
console.log(`[${level}] ${message}`);
}
}
class TimestampLogger extends BaseLogger {
log(level: string, message: string): void {
const timestamp = new Date().toISOString();
super.log(level, `[${timestamp}] ${message}`);
}
}
class FilteredLogger extends BaseLogger {
private minLevel: number = 0;
setMinLevel(level: number): void {
this.minLevel = level;
}
log(level: string, message: string): void {
const levelMap: Record<string, number> = { info: 0, warn: 1, error: 2 };
if ((levelMap[level] ?? 0) >= this.minLevel) {
super.log(level, message);
}
}
}
const logger = new TimestampLogger();
logger.log("info", "Server started");
// [info] [2026-06-28T12:00:00.000Z] Server started
Constructor Inheritance
class Employee {
constructor(
public name: string,
public salary: number
) {}
}
class Manager extends Employee {
constructor(
name: string,
salary: number,
public department: string
) {
super(name, salary * 1.2); // Managers get 20% more
}
}
class Intern extends Employee {
constructor(name: string) {
super(name, 0); // Unpaid
}
}
const manager = new Manager("Alice", 80000, "Engineering");
console.log(manager.salary); // 96000
Polymorphism
Polymorphism lets you treat different subclass instances through the same parent type:
abstract class PaymentMethod {
abstract process(amount: number): string;
abstract getType(): string;
}
class CreditCard extends PaymentMethod {
process(amount: number): string {
return `Charged $${amount} to credit card`;
}
getType(): string { return "Credit Card"; }
}
class PayPal extends PaymentMethod {
process(amount: number): string {
return `Charged $${amount} via PayPal`;
}
getType(): string { return "PayPal"; }
}
class Crypto extends PaymentMethod {
process(amount: number): string {
return `Charged $${amount} in cryptocurrency`;
}
getType(): string { return "Crypto"; }
}
function processPayment(method: PaymentMethod, amount: number): void {
console.log(`[${method.getType()}] ${method.process(amount)}`);
}
const payments: PaymentMethod[] = [
new CreditCard(),
new PayPal(),
new Crypto(),
];
for (const payment of payments) {
processPayment(payment, 99.99);
}
// [Credit Card] Charged $99.99 to credit card
// [PayPal] Charged $99.99 via PayPal
// [Crypto] Charged $99.99 in cryptocurrency
The protected Access Modifier
Protected members are accessible in subclasses but not from outside:
class Animal {
protected dna: string = "ATCG";
private heartbeat(): void {} // Not inherited
}
class Dog extends Animal {
constructor() {
super();
console.log(this.dna); // OK — protected
// this.heartbeat(); // Error — private
}
}
const dog = new Dog();
// dog.dna; // Error — protected
Mixins
TypeScript doesn't support multiple inheritance, but mixins simulate it:
// Mixin functions
function Timestamped<TBase extends new (...args: any[]) => object>(Base: TBase) {
return class extends Base {
createdAt = new Date();
getAge(): number {
return Date.now() - this.createdAt.getTime();
}
};
}
function Activatable<TBase extends new (...args: any[]) => object>(Base: TBase) {
return class extends Base {
isActive: boolean = false;
activate(): void { this.isActive = true; }
deactivate(): void { this.isActive = false; }
};
}
class UserBase {
constructor(public name: string) {}
}
// Apply mixins
const ActiveTimestampedUser = Activatable(Timestamped(UserBase));
class User extends ActiveTimestampedUser {
constructor(name: string) {
super(name);
}
greet(): string {
return `Hello, I'm ${this.name}`;
}
}
const user = new User("Alice");
user.activate();
console.log(user.greet()); // Hello, I'm Alice
console.log(user.isActive); // true
console.log(user.createdAt instanceof Date); // true
Common Mistakes
1. Forgetting to Call super()
class Parent {
constructor(public name: string) {}
}
class Child extends Parent {
constructor(name: string) {
// super(name); // Error: Must call super in derived class before accessing 'this'
}
}
2. Calling super() After Accessing this
class Child extends Parent {
constructor(name: string) {
// this.greeting = "Hi"; // Error: 'super' must be called before accessing 'this'
super(name);
this.greeting = "Hi"; // OK
}
private greeting!: string;
}
3. Overriding Without Calling Super When Needed
class Parent {
init(): void { console.log("Parent init"); }
}
class Child extends Parent {
init(): void {
// Forgot to call super.init() — parent initialization is skipped
console.log("Child init");
}
}
4. Confusing Inheritance with Composition
Inheritance models "is-a". Composition models "has-a". Favor composition over inheritance.
// Inheritance (is-a): Dog IS an Animal
class Dog extends Animal {}
// Composition (has-a): Car HAS an Engine
class Car {
constructor(private engine: Engine) {}
}
5. Creating Deep Inheritance Hierarchies
More than 2-3 levels of inheritance usually signals a design problem. Prefer composition or interfaces.
6. Not Using protected Constructors for Abstract-Like Behavior
class BaseClass {
protected constructor() {} // Cannot be instantiated directly, but no abstract methods
}
Practice Questions
What must a subclass constructor call first?
super()with the parent constructor's required arguments.What is the difference between
privateandprotected?privatemembers are not accessible in subclasses.protectedmembers are.What is polymorphism? The ability to use objects of different types through a common interface, where the correct method is determined at runtime.
How do mixins simulate multiple inheritance? By using functions that take a base class and return a new class extending it, then combining them.
Challenge: Create an inheritance hierarchy for a document processing system: Document (base) → PDFDocument, SpreadsheetDocument, TextDocument. Each subclass implements render(): string and parse(): void differently. Use polymorphism to process a mixed array.
FAQ
Mini Project: Notification System
// src/notifications.ts
abstract class Notification {
constructor(
protected recipient: string,
protected message: string
) {}
abstract send(): Promise<boolean>;
abstract getType(): string;
protected log(success: boolean): void {
console.log(`[${this.getType()}] To: ${this.recipient} — ${success ? "Sent" : "Failed"}`);
}
}
class EmailNotification extends Notification {
async send(): Promise<boolean> {
console.log(`Sending email to ${this.recipient}: ${this.message}`);
this.log(true);
return true;
}
getType(): string { return "Email"; }
}
class SMSNotification extends Notification {
async send(): Promise<boolean> {
console.log(`Sending SMS to ${this.recipient}: ${this.message}`);
this.log(true);
return true;
}
getType(): string { return "SMS"; }
}
class PushNotification extends Notification {
async send(): Promise<boolean> {
console.log(`Sending push to ${this.recipient}: ${this.message}`);
this.log(true);
return true;
}
getType(): string { return "Push"; }
}
class NotificationService {
private notifications: Notification[] = [];
add(notification: Notification): void {
this.notifications.push(notification);
}
async sendAll(): Promise<{ sent: number; failed: number }> {
let sent = 0;
let failed = 0;
for (const notification of this.notifications) {
const success = await notification.send();
if (success) sent++; else failed++;
}
return { sent, failed };
}
}
const service = new NotificationService();
service.add(new EmailNotification("alice@example.com", "Scan complete"));
service.add(new SMSNotification("+1234567890", "Threat detected!"));
service.add(new PushNotification("device-abc", "Update available"));
const result = await service.sendAll();
console.log(`Sent: ${result.sent}, Failed: ${result.failed}`);
What's Next
Now explore decorators for Metaprogramming:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/20-abstract-classes" >}} | Review abstract classes |
| {{< ref "/programming-languages/typescript/22-decorators" >}} | Class, method, property, parameter decorators |
| {{< ref "/programming-languages/typescript/23-this-typing" >}} | This parameter binding and typing |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro