Abstract Classes
An abstract class is a base class that cannot be instantiated on its own and may declare members without an implementation. It sits between an interface and a concrete class: like an interface it defines a contract, but unlike an interface it can also provide shared, ready-to-use behavior. Subclasses must implement every abstract member before they become concrete. This page covers declaring abstract classes and methods, mixing abstract and concrete members, abstract accessors, and when to choose an abstract class over an interface.
Declaring an abstract class
Mark a class abstract to prevent direct instantiation. It can still have a constructor, fields, and fully implemented methods — those are inherited normally.
abstract class Animal {
constructor(public name: string) {}
abstract makeSound(): string; // no body — subclasses must provide one
move(): string {
return `${this.name} moves`;
}
}
new Animal("Rex"); // ❌ Error: Cannot create an instance of an abstract class
Animal defines a shared move method and a name field, but makeSound is abstract — it has a signature but no body. You cannot write new Animal(...) because the class is incomplete.
Implementing abstract members
A subclass must implement every abstract member, or it too must be declared abstract. Once all abstract members are provided, the subclass is concrete and instantiable.
class Dog extends Animal {
makeSound(): string {
return "Woof";
}
}
class Cat extends Animal {
// ❌ Error: Non-abstract class 'Cat' does not implement
// inherited abstract member 'makeSound'
}
const d = new Dog("Rex");
d.makeSound(); // "Woof"
d.move(); // "Rex moves" (inherited)
Dog supplies makeSound, so it is concrete. Cat forgets to, so the compiler rejects it until you either implement the method or mark Cat abstract.
Abstract properties and accessors
Besides methods, you can declare abstract fields and abstract getters/setters. Subclasses must provide a matching concrete member.
abstract class Shape {
abstract readonly name: string;
abstract get area(): number;
describe(): string {
return `${this.name} has area ${this.area}`;
}
}
class Square extends Shape {
readonly name = "Square";
constructor(private side: number) {
super();
}
get area(): number {
return this.side ** 2;
}
}
new Square(4).describe(); // "Square has area 16"
The base describe method relies on the abstract name and area members, which Square fills in. This is the template-method pattern: shared logic in the base, the variable parts deferred to subclasses.
Abstract class vs interface
Both define contracts, but they differ in capability and runtime cost. An interface is purely a compile-time shape with no JavaScript output; an abstract class is a real class that can carry implementation and state.
| Feature | Abstract class | Interface |
|---|---|---|
| Provides implementation | Yes | No |
| Holds state / fields with values | Yes | No |
| Constructor | Yes | No |
| Runtime presence | Yes (emits code) | No (erased) |
| Multiple inheritance | One per class (extends) | Many (implements A, B) |
| Access modifiers | Yes (protected, etc.) | No |
Choose an abstract class when subclasses should share concrete behavior or state. Choose an interface when you only need to describe a shape, especially if a type must implement several contracts at once.
Abstract constructor signatures
You can type a value as “a constructor for some subclass” using an abstract construct signature. This is useful for factories that accept class references.
abstract class Base {
abstract id: string;
}
function instantiate<T extends Base>(Ctor: new () => T): T {
return new Ctor();
}
class Concrete extends Base {
id = "x";
}
instantiate(Concrete); // ✅
instantiate(Base); // ❌ Error: abstract class cannot be assigned to `new () => T`
The new () => T parameter requires a concrete (non-abstract) constructor, so passing Base is correctly rejected.
Best Practices
- Use abstract classes for “is-a” hierarchies that share both behavior and a contract.
- Keep abstract members minimal — only the parts that genuinely vary between subclasses.
- Put common, stable logic in concrete methods on the abstract base to avoid duplication.
- Prefer an interface when you need multiple inheritance or zero runtime footprint.
- Combine an abstract method with concrete template methods (template-method pattern).
- Mark base-only helpers
protectedso subclasses can use them but consumers cannot. - Do not expose an abstract class’s incomplete state through public fields.