Abstract Classes in TypeScript

Abstract classes define a contract that subclasses must fulfill. Learn how to use the abstract keyword to create base classes that cannot be instantiated directly.

7 min read

An abstract class in TypeScript is a class that cannot be instantiated on its own. It exists only to be extended by subclasses that fill in the missing pieces. You mark a class as abstract with the keyword, and you mark individual methods or fields that subclasses must provide.

typescripttypescript
abstract class Shape {
  abstract area(): number;
 
  describe(): string {
    return `This shape has an area of ${this.area()}.`;
  }
}

Shape has one abstract method (area) that subclasses must implement, and one regular method (describe) that subclasses inherit as-is. You cannot write new Shape() because area has no body.

Here is a concrete subclass that fills in the missing method:

typescripttypescript
class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }
 
  area(): number {
    return Math.PI * this.radius ** 2;
  }
}
 
const circle = new Circle(5);
console.log(circle.describe());

This prints the sentence below, since describe calls this.area() internally and Circle supplies the concrete implementation of that abstract method:

texttext
This shape has an area of 78.54.

Circle provides the missing area method, so it is concrete and can be instantiated. It inherits describe from Shape without writing any extra code.

What Happens When You Forget an Abstract Member

If a subclass does not implement every abstract member from its base class, TypeScript reports an error and the subclass itself must also be marked abstract:

typescripttypescript
abstract class Shape {
  abstract area(): number;
  abstract perimeter(): number;
}
 
class Square extends Shape {
  area(): number {
    return 0;
  }
}

TypeScript blocks this because perimeter is missing. The error message names the exact member that was forgotten, making it easy to find what you need to add.

Abstract Fields

You can also declare abstract properties that subclasses must provide. The syntax looks like a field declaration with no initializer:

typescripttypescript
abstract class Config {
  abstract apiUrl: string;
 
  fetchData(): string {
    return `Fetching from ${this.apiUrl}...`;
  }
}
 
class AppConfig extends Config {
  apiUrl = "https://api.example.com";
}
 
const config = new AppConfig();
console.log(config.fetchData());

This prints the line below, since fetchData reads this.apiUrl and AppConfig is the concrete subclass that actually supplies that value:

texttext
Fetching from https://api.example.com...

The abstract apiUrl field forces every concrete subclass to declare its own apiUrl property. The base class can use this.apiUrl in its methods, trusting that subclasses will provide a value.

Abstract Construct Signatures

When you write a function that accepts a class constructor, TypeScript prevents you from passing an abstract class. You must specify that the constructor produces a concrete instance:

typescripttypescript
abstract class Base {
  abstract getName(): string;
  printName() {
    console.log("Hello, " + this.getName());
  }
}
 
class Derived extends Base {
  getName() { return "world"; }
}

Derived implements the abstract getName method, so it is concrete and can be instantiated. Now let us write a function that accepts a class constructor and uses it to create an instance:

typescripttypescript
function greet(Ctor: new () => Base) {
  const instance = new Ctor();
  instance.printName();
}
 
greet(Derived);
greet(Base);

Passing Derived works fine. Passing Base produces an error because you cannot create an instance of an abstract class. The type new () => Base in the function signature tells TypeScript that Ctor must be a constructor that produces a valid instance of Base.

Abstract Classes vs Interfaces

Both define contracts that other types must satisfy, but they serve different purposes:

Abstract ClassInterface
Can have implemented methodsYesNo
Can have fields with valuesYesNo
Can be instantiatedNoNo
Multiple inheritanceNo (one base class)Yes (multiple interfaces)
Exists at runtimeYes (as a class)No

Use an abstract class when you want to share implementation code across subclasses. Use an interface when you only care about the shape and do not need to share any logic.

When to Use Abstract Classes

Abstract classes work best when:

  • Multiple classes share common behavior but differ in one or two specific ways.
  • You want to enforce that every subclass provides certain capabilities (like a template method pattern).
  • You need runtime-available base class logic that interfaces cannot provide.

Skip abstract classes when:

  • You only need a shape contract (use an interface).
  • Every subclass would override every method anyway (an interface is simpler).
  • You need multiple inheritance (use interfaces).

Common Mistakes

Trying to instantiate an abstract class. TypeScript catches this at compile time. If you see an error about creating an instance of an abstract class, either remove the abstract keyword or extend it with a concrete class.

Forgetting to implement an abstract member. The subclass itself must be marked abstract until every abstract member is filled in. The error message tells you exactly which member is missing.

Mixing up abstract classes and interfaces. An abstract class can have implemented methods. An interface cannot. Choose based on whether you need to share behavior or just define a shape.

For how classes relate to each other through extends and implements, see classes in TypeScript explained and implements keyword in TypeScript. For choosing between inheritance patterns, see inheritance vs composition in TypeScript.

Rune AI

Rune AI

Key Insights

  • An abstract class cannot be instantiated directly. Only concrete subclasses can be created with new.
  • Abstract methods and fields have no implementation in the base class. Subclasses must provide them.
  • An abstract class can also include regular methods with full implementations that subclasses inherit.
  • Abstract construct signatures let you write functions that accept a concrete subclass constructor but not the abstract base.
  • The abstract keyword only exists at compile time. At runtime, abstract classes are plain JavaScript classes.
RunePowered by Rune AI

Frequently Asked Questions

Can I instantiate an abstract class directly?

No. TypeScript reports an error if you try to call new on an abstract class. You must create a concrete subclass that implements all abstract members before you can create an instance.

Can an abstract class have non-abstract methods?

Yes. Abstract classes can mix abstract members (which subclasses must implement) with fully implemented methods (which subclasses inherit). This is the main reason to use abstract classes over interfaces.

Can I have abstract fields in TypeScript?

Yes. TypeScript supports abstract property declarations in abstract classes. Subclasses must provide their own implementation of the field, typically by declaring it with the same name.

Conclusion

Abstract classes sit between interfaces and concrete classes. They let you define a shared contract that subclasses must follow while also providing some ready-made implementation. Use abstract when you want to enforce that every subclass provides certain methods, but you also want to share common logic. Remember that abstract is a compile-time concept: it disappears from the emitted JavaScript.