Typescript Inheritance And Abstract Classes Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    10 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of TypeScript Inheritance and Abstract Classes

TypeScript Inheritance and Abstract Classes: A Detailed Guide

Understanding Inheritance

Inheritance is a fundamental concept in object-oriented programming that allows objects or classes to inherit properties and methods from other objects or classes. This mechanism promotes code reusability and establishes a hierarchical relationship between classes.

  • Extending Classes: When a class inherits from another class, it is said to extend or subclass that class. The subclass includes all the properties and methods of the superclass (also known as the base class), unless those properties and methods are overridden or hidden.
Example:
class Animal {
    type: string;

    constructor(type: string) {
        this.type = type;
    }

    makeSound(): void {
        console.log("Some generic sound");
    }
}

class Dog extends Animal {
    breed: string;

    constructor(type: string, breed: string) {
        super(type); // Calls the parent class' constructor
        this.breed = breed;
    }

    makeSound(): void {
        console.log("Woof woof");
    }
}

const myDog = new Dog("Canine", "Labrador");
console.log(myDog.type); // Outputs: Canine
console.log(myDog.breed); // Outputs: Labrador
myDog.makeSound(); // Outputs: Woof woof

In this example, Dog is a subclass of Animal. It inherits the type property and the makeSound() method, yet it overrides makeSound() to define a specific behavior for dogs.

  • Super Keyword: The super keyword is used to refer to the parent class's constructor, methods, or properties. It helps in calling the superclass constructor during instantiation and invoking superclass methods within subclass methods.
Example:
class Cat extends Animal {
    name: string;

    constructor(type: string, name: string) {
        super(type);
        this.name = name;
    }

    introduce(): void {
        console.log(`I am a ${this.type} named ${this.name}`);
        super.makeSound(); // Calling the superclass method
    }
}

const myCat = new Cat('Feline', 'Whiskers');
myCat.introduce(); // Outputs: I am a Feline named Whiskers
//                  Some generic sound

In this example, super(type) calls the Animal constructor, and super.makeSound() uses the makeSound method defined in Animal.

  • Access Modifiers: Access modifiers determine the visibility and accessibility of class members. They include public, protected, and private.
    • public: The member is accessible from anywhere.
    • protected: The member is accessible only within its containing class and any classes that derive from it.
    • private: The member is accessible only within its containing class.

Role of Abstract Classes

Abstract classes serve as a blueprint for other classes. They cannot be instantiated directly and are used to define properties and methods that must be included in any subclass derived from them.

  • Abstract Methods: Abstract classes can contain abstract methods, which do not have an implementation and must be implemented by subclasses.
Example:
abstract class Vehicle {
    abstract move(): void; // Abstract method
    startEngine(): void {
        console.log('Engine started');
    }
}

class Car extends Vehicle {
    move(): void { // Implementation of abstract method
        console.log('Car is moving');
    }
}

const myCar = new Car();
myCar.startEngine(); // Outputs: Engine started
myCar.move(); // Outputs: Car is moving

// const v = new Vehicle(); // Compile-time error: Cannot create an instance of an abstract class

In the above code, Vehicle is an abstract class with one abstract method move() and one concrete method startEngine(). The Car class extends Vehicle and provides an implementation for the move() method.

  • Purpose: Abstract classes are useful when you want to enforce certain behaviors in any derived classes. They centralize common code and provide a template for more specialized classes, promoting design patterns and reducing redundancy.

Important Information

  1. Single Inheritance: Unlike languages that allow multiple inheritance, TypeScript supports single inheritance, meaning each class can inherit from exactly one parent class.

  2. Constructor Function: If a subclass has a constructor, it must call super() to initialize the parent class properties.

  3. Method Overriding: Subclasses can override methods inherited from their superclass to provide a more specific implementation.

  4. Static Members: Static members, whether they are properties or methods, belong to the class itself rather than instances of the class. Inheritance does not impact static members.

  5. Interfaces: While abstract classes are used more for establishing a hierarchy and sharing common functionality, interfaces are often better suited for defining common functionality across various unrelated classes.

  6. Access Control: Proper use of access modifiers (public, protected, private) is crucial for encapsulating data and preventing unauthorized access or modification.

  7. Performance Consideration: Although inheritance in TypeScript is powerful, overusing it can lead to complex hierarchies and potential performance issues. It’s essential to use inheritance judiciously and consider composition or mixins as alternatives.

Practical Applications

  • Code Reusability: Inheritance makes code easier to use and develop across different parts of an application, especially in large-scale systems where similar functionalities are required in multiple classes.

  • Polymorphism: It allows polymorphic behavior, which means a function can operate on objects of different types through common behavior defined by a superclass.

  • Template Method Pattern: Abstract classes can be used to implement template method patterns, where the skeleton of an algorithm is defined in the superclass but subclasses are allowed to provide steps of the algorithm.

Example:
abstract class Game {
    abstract play(): void;

    loadAssets() {
        console.log('Loading assets...');
    }
    
    playGame() {
        this.loadAssets();
        this.play();
        this.scoreDisplay();
    }
    
    scoreDisplay() {
        console.log('Displaying score...');
    }
}

class Chess extends Game {
    play(): void {
        console.log('Playing chess...');
    }
}

const chessGame = new Chess();
chessGame.playGame();
/*
Outputs:
Loading assets...
Playing chess...
Displaying score...
*/

In this example, Chess class implements the play method and inherits loadAssets() and scoreDisplay() from Game, allowing for a consistent flow of gameplay without duplicating code.

Understanding and effectively utilizing inheritance and abstract classes in TypeScript enables developers to write more organized, scalable, and maintainable code, aligning with modern software development practices.

Conclusion

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement TypeScript Inheritance and Abstract Classes

Example 1: Basic Inheritance

Inheritance allows a class to inherit properties and methods from another class. The class that inherits is called the derived or child class, and the class being inherited from is known as the base or parent class.

Step-by-Step Implementation

  1. Define a Base Class: Create a Vehicle class that has common properties and methods for all types of vehicles.
  2. Define a Derived Class: Create a Car class that extends the Vehicle class and adds its own specific features.
// Base Class: Vehicle
class Vehicle {
    protected make: string;
    protected model: string;
    protected year: number;

    constructor(make: string, model: string, year: number) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    getDetails() {
        return `${this.year} ${this.make} ${this.model}`;
    }

    startEngine() {
        console.log("Engine started");
    }

    stopEngine() {
        console.log("Engine stopped");
    }
}

// Derived Class: Car
class Car extends Vehicle {
    private numberOfDoors: number;

    constructor(make: string, model: string, year: number, numberOfDoors: number) {
        // Call the constructor of the base class
        super(make, model, year);
        this.numberOfDoors = numberOfDoors;
    }

    getCarDetails() {
        return `${this.getDetails()} with ${this.numberOfDoors} doors`;
    }
}

const myCar = new Car('Toyota', 'Camry', 2022, 4);
console.log(myCar.getCarDetails());   // Output: 2022 Toyota Camry with 4 doors
myCar.startEngine();                  // Output: Engine started
myCar.stopEngine();                   // Output: Engine stopped

Explanation:

  • Vehicle Class: Serves as a base class with protected properties (make, model, year). These are accessible within the Vehicle class and any class that extends it.
  • Constructor in Vehicle: Initializes the properties of a vehicle.
  • Methods in Vehicle: Common functionalities like startEngine() and stopEngine().
  • Car Class: Inherits from Vehicle using the extends keyword.
  • super Keyword: Calls the constructor of the base class (Vehicle), passing necessary parameters.
  • Car Specific Properties: Adds a numberOfDoors property specific to cars.
  • getCarDetails Method: Utilizes the method from the base class (getDetails) to create a detailed description.

Example 2: Using Access Modifiers

Access modifiers in TypeScript (private, protected, public) control the visibility of class members.

Step-by-Step Implementation

  1. Define a Base Class Animal: It should include private and protected properties.
  2. Define a Derived Class Dog: It should extend Animal and add specific behaviors.
// Base Class: Animal
class Animal {
    private name: string;
    protected species: string;

    constructor(name: string, species: string) {
        this.name = name;
        this.species = species;
    }

    getName() {
        return this.name;
    }

    makeSound() {
        console.log(`${this.species} makes a sound`);
    }
}

// Derived Class: Dog
class Dog extends Animal {
    private breed: string;

    constructor(name: string, species: string, breed: string) {
        super(name, species);
        this.breed = breed;
    }

    getBreed() {
        return this.breed;
    }

    makeSound() {
        super.makeSound();
        console.log(`Specifically, ${this.getName()} the ${this.breed} barks`);
    }
}

const myDog = new Dog('Buddy', 'Canine', 'Golden Retriever');
console.log(myDog.getBreed());         // Output: Golden Retriever
myDog.makeSound();                     // Output: Canine makes a sound
                                       //         Specifically, Buddy the Golden Retriever barks

Explanation:

  • Private Property (name): This property is only accessible within the Animal class.
  • Protected Property (species): This property can be accessed within the Animal class and any class that extends it (Dog).
  • Public Methods (getName and makeSound): getName returns the name of the animal, while makeSound logs a generic message.
  • Overriding Methods: The Dog class overrides the makeSound method to provide species-specific behavior, calling the superclass method using super.

Example 3: Abstract Classes

An abstract class cannot be instantiated on its own and must be inherited by other classes. Abstract classes often contain one or more abstract methods that do not have an implementation but must be implemented by derived classes.

Step-by-Step Implementation

  1. Define an Abstract Class Shape:
    • Includes abstract properties that need to be defined by subclasses.
    • Includes an abstract method area that needs to be implemented.
  2. Define a Concrete Class Circle:
    • Extends Shape and provides an implementation for the abstract properties and method.
// Abstract Class: Shape
abstract class Shape {
    protected width: number;
    protected height: number;

    constructor(width: number, height: number) {
        this.width = width;
        this.height = height;
    }

    abstract area(): number;

    // Non-abstract method (optional)
    getDimensions(): { width: number, height: number } {
        return { width: this.width, height: this.height };
    }
}

// Concrete Class: Circle
class Circle extends Shape {
    // Override the width and height to represent the radius of the circle
    private radius: number;

    constructor(radius: number) {
        super(radius * 2, radius * 2); // width and height are diameter for a circle
        this.radius = radius;
    }

    // Implementing the abstract method
    area(): number {
        return Math.PI * this.radius * this.radius;
    }
}

const myCircle = new Circle(5);
console.log(myCircle.getDimensions());     // Output: { width: 10, height: 10 }
console.log(`Area of circle: ${myCircle.area()}`); // Output: Area of circle: 78.53981633974483

Explanation:

  • Abstract Class (Shape): Defines template properties (width, height) and an abstract method (area).
  • Cannot Instantiate Shape: Trying to create an instance of Shape directly would result in an error.
  • Concrete Derived Class (Circle): Inherits from Shape, initializes properties, and implements the abstract method (area).

Example 4: Combining Inheritance and Abstract Classes

This example combines concepts from the previous examples by creating an abstract class that can serve as a template for various shapes, each of which can extend and implement the abstract class methods accordingly.

Step-by-Step Implementation

  1. Define an Abstract Class Shape:
    • Includes abstract method area().
    • Includes non-abstract method getDimensions().
  2. Define Derived Classes Circle and Rectangle:
    • Both extend Shape and provide implementations for the abstract method area().
// Abstract Class: Shape
abstract class Shape {
    protected width: number;
    protected height: number;

    constructor(width: number, height: number) {
        this.width = width;
        this.height = height;
    }

    abstract area(): number;

    getDimensions(): { width: number, height: number } {
        return { width: this.width, height: this.height };
    }
}

// Derived Class: Circle
class Circle extends Shape {
    private radius: number;

    constructor(radius: number) {
        super(radius * 2, radius * 2); // Diameter for width and height
        this.radius = radius;
    }

    area(): number {
        return Math.PI * this.radius * this.radius;
    }
}

// Derived Class: Rectangle
class Rectangle extends Shape {
    constructor(width: number, height: number) {
        super(width, height);
    }

    area(): number {
        return this.width * this.height;
    }
}

const myCircle = new Circle(5);
console.log(myCircle.getDimensions()); // Output: { width: 10, height: 10 }
console.log(`Area of circle: ${myCircle.area()}`); // Output: Area of circle: 78.53981633974483

const myRectangle = new Rectangle(3, 7);
console.log(myRectangle.getDimensions()); // Output: { width: 3, height: 7 }
console.log(`Area of rectangle: ${myRectangle.area()}`); // Output: Area of rectangle: 21

Explanation:

  • Abstract Class (Shape): Provides a common interface for different shapes.
  • Derived Class (Circle): Implements the area() method using the radius property.
  • Derived Class (Rectangle): Implements the area() method using the width and height properties.

Example 5: Inherited Constructor Overriding

Sometimes you might want to override the constructor in a derived class to have a different constructor signature or additional behavior. Here's how:

Step-by-Step Implementation

  1. Define a Base Class Person:
    • Has a constructor initializing firstName and lastName.
  2. Define a Concrete Class Employee:
    • Inherits from Person.
    • Overrides the constructor to also take an employeeId.

Top 10 Interview Questions & Answers on TypeScript Inheritance and Abstract Classes

Top 10 Questions and Answers on TypeScript Inheritance and Abstract Classes

Inheritance is a fundamental concept in TypeScript (and many other languages) that allows a class to inherit properties and methods from a parent class. This promotes code reusability and establishes a clear hierarchy between classes.

Example:

// Base class
class Animal {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

// Derived class
class Dog extends Animal {
    breed: string;

    constructor(name: string, breed: string) {
        super(name); // Calls the constructor of the base class
        this.breed = breed;
    }

    speak() {
        console.log(`${this.name} barks.`);
    }
}

const myDog = new Dog("Buddy", "Golden Retriever");
myDog.speak(); // Outputs: Buddy barks.

2. What is the difference between super and this in TypeScript?

  • this refers to the current instance of the class.
  • super is used to invoke the constructor of the parent class, allowing you to access members of the parent class.

3. How do abstract classes work in TypeScript?

Abstract classes are used to provide a base class from which other classes may be derived. They cannot be instantiated directly. An abstract class can contain both abstract methods (which do not contain an implementation and must be implemented in derived classes) and non-abstract methods.

Example:

abstract class Animal {
    abstract makeSound(): void;

    move(): void {
        console.log("Moving along!");
    }
}

class Dog extends Animal {
    makeSound() {
        console.log("Woof! Woof!");
    }
}

let myDog = new Dog();
myDog.makeSound(); // Outputs: Woof! Woof!
myDog.move(); // Outputs: Moving along!

4. Can abstract classes have non-abstract methods?

Yes, abstract classes can definitely have non-abstract methods. These methods have an implementation and can be called directly on instances of derived classes.

5. What happens if a derived class does not implement an abstract method from its base abstract class?

If a derived class does not implement an abstract method from its base abstract class, it must also be declared as abstract.

Example:

abstract class Animal {
    abstract makeSound(): void;
}

class Dog extends Animal {
    // This implementation is missing, so Dog must also be abstract.
}

// const myDog = new Dog(); // Error: Cannot create an instance of an abstract class.

6. Can a class extend multiple classes in TypeScript?

No, TypeScript does not support multiple class inheritance. A class can only extend one base class. However, it can implement multiple interfaces, which can be used to achieve some of the benefits of multiple inheritance.

7. What is the purpose of the protected modifier in TypeScript, and how does it relate to inheritance?

The protected modifier in TypeScript allows properties and methods to be accessed only within the class itself and by derived classes, but not from outside the class hierarchy. This is useful for defining internal state that is not exposed to the public but needs to be shared among base and derived classes.

Example:

class Animal {
    protected species: string;

    constructor(species: string) {
        this.species = species;
    }
}

class Dog extends Animal {
    speak() {
        console.log(`The ${this.species} barks.`);
    }
}

const myDog = new Dog("Dog");
// myDog.species; // Error: Property 'species' is protected and only accessible within class 'Animal' and its subclasses.

8. What is method overriding in TypeScript?

Method overriding is when a derived class provides a specific implementation for a method that is already defined in its base class. This allows the derived class to modify or extend the functionality of the inherited method.

Example:

class Animal {
    speak() {
        console.log("Some generic animal sound");
    }
}

class Dog extends Animal {
    speak() {
        console.log("Woof! Woof!");
    }
}

const myDog = new Dog();
myDog.speak(); // Outputs: Woof! Woof!

9. Can interfaces be used with abstract classes in TypeScript?

Yes, interfaces can be used with abstract classes. An abstract class can implement an interface, and derived classes must implement any abstract methods defined in the abstract class and any methods defined in the interface.

Example:

interface Speaker {
    makeSound(): void;
}

abstract class Animal implements Speaker {
    abstract makeSound(): void;
}

class Dog extends Animal {
    makeSound() {
        console.log("Woof! Woof!");
    }
}

const myDog = new Dog();
myDog.makeSound(); // Outputs: Woof! Woof!

10. When should you use abstract classes versus interfaces in TypeScript?

  • Abstract Classes: Use abstract classes when you need a base class that provides shared implementation, such as methods and properties, that can be used by derived classes. Abstract classes are also useful when you need to prevent the instantiation of a class directly.

  • Interfaces: Use interfaces when you need to define a contract that can be implemented by any class, regardless of how it is implemented. Interfaces are useful for creating a loosely coupled system where the specific implementations can vary widely. Interfaces are also used for defining the shape of an object without providing any implementation details.

You May Like This Related .NET Topic

Login to post a comment.