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.
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.
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:
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:
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:
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:
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:
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:
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:
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 Class | Interface | |
|---|---|---|
| Can have implemented methods | Yes | No |
| Can have fields with values | Yes | No |
| Can be instantiated | No | No |
| Multiple inheritance | No (one base class) | Yes (multiple interfaces) |
| Exists at runtime | Yes (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
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.
Frequently Asked Questions
Can I instantiate an abstract class directly?
Can an abstract class have non-abstract methods?
Can I have abstract fields in TypeScript?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.