How to Use Abstract Classes in TypeScript

Use abstract classes in TypeScript when multiple classes should share a base structure while forcing subclasses to implement required behavior. This is perfect for UI frameworks, domain models, game entities, and reusable service contracts.

What you’ll build or solve

You’ll learn how to use abstract classes in TypeScript with shared logic, abstract methods, and subclass implementations. You’ll also know when abstract classes are better than interfaces.

When this approach works best

This approach is the right choice when subclasses need both shared implementation and required method contracts.

Common real-world scenarios include:

  • Base UI components
  • Payment providers
  • Shape renderers
  • Game entities
  • Service adapters

This is a bad idea when only the method shape matters and no shared implementation is needed.

Prerequisites

You only need:

  • Basic TypeScript classes
  • Inheritance
  • Methods
  • Access modifiers

Step-by-step instructions

Step 1: Create an abstract base class

Use the abstract keyword.

abstract class Shape {
  constructor(public color: string) {}

  abstract getArea(): number;
}

This class cannot be instantiated directly.

It defines shared structure plus a required method.

Step 2: Extend the abstract class

Child classes must implement all abstract methods.

class Circle extends Shape {
  constructor(color: string, public radius: number) {
    super(color);
  }

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

Now the subclass becomes valid and reusable.

Step 3: Add shared concrete logic

Abstract classes can still include reusable methods.

abstract class Animal {
  constructor(public name: string) {}

  describe(): string {
    return `Animal: ${this.name}`;
  }

  abstract speak(): string;
}

Subclasses reuse describe() while defining speak().

What to look for:

  • abstract prevents direct instantiation
  • Shared logic can live in the base class
  • Abstract methods must be implemented
  • Great for reusable contracts + logic
  • Better than interfaces when shared code exists

Examples you can copy

Payment provider

abstract class PaymentProvider {
  abstract charge(amount: number): void;
}

Base UI widget

abstract class Widget {
  abstract render(): string;
}

Game character

abstract class Enemy {
  abstract attack(): void;
}

Common mistakes and how to fix them

Mistake 1: Trying to instantiate the abstract class

What the reader might do:

const shape = new Shape("red");

Why it breaks: abstract classes cannot create direct instances.

Corrected approach:

Instantiate a concrete subclass.

Mistake 2: Forgetting to implement abstract methods

What the reader might do:

Extend the base class without defining getArea().

Why it breaks: the subclass stays abstract and cannot be instantiated.

Corrected approach:

Implement every abstract member.

Mistake 3: Using abstract classes when only types matter

What the reader might do:

Use an abstract class with no shared logic.

Why it breaks: an interface is simpler.

Corrected approach:

Use interfaces when behavior sharing is unnecessary.

Troubleshooting

If the subclass errors, implement all abstract methods.

If the base class is never meant to share logic, switch to an interface.

If new fails, instantiate the concrete subclass instead.

If the hierarchy gets too rigid, consider composition.

Quick recap

  • Use abstract classes for shared logic + required contracts
  • They cannot be instantiated directly
  • Subclasses must implement abstract methods
  • Great for reusable frameworks
  • Prefer interfaces when no shared logic exists