Base Classes That Can't Be Instantiated
Abstract Classes
`abstract` classes can't be `new`-ed directly. They declare shape + partial implementation; subclasses fill in the rest.
What you'll learn
- Declare an abstract class with abstract methods
- Use a non-abstract base method for shared logic
- Subclass to fill in the abstract parts
An abstract class can’t be instantiated. It exists to be extended — the base class declares some methods that subclasses must implement, plus shared logic that they inherit.
The Syntax
abstract class Shape {
abstract area(): number; // no body — subclass must define
describe() {
return `area = ${this.area().toFixed(2)}`;
}
}
class Circle extends Shape {
constructor(public r: number) { super(); }
area() { return Math.PI * this.r ** 2; }
}
class Square extends Shape {
constructor(public side: number) { super(); }
area() { return this.side ** 2; }
}
new Shape(); // ✗ Cannot create an instance of an abstract class
new Circle(2).describe(); // "area = 12.57" abstract on the class blocks new Shape(). abstract area()
forces every subclass to implement it.
Why?
Two reasons:
- Force a contract. Subclasses must provide an
area(). If they don’t, TS errors. - Share base logic.
describe()is defined once in the base — every subclass gets it for free.
You can think of it as interface + some shared implementation,
in one declaration.
Mixing Abstract and Concrete
abstract class Animal {
constructor(public name: string) {}
abstract speak(): string; // subclass must implement
greet() { // shared
return `${this.name} says ${this.speak()}`;
}
}
class Cat extends Animal {
speak() { return "meow"; }
}
new Cat("Salem").greet(); // "Salem says meow" Trade-offs
Modern JS leans on composition over inheritance. Abstract classes are valuable when:
- You have a stable hierarchy (shapes, AST nodes, parser states)
- The base genuinely shares implementation
For loose plug-in style, an interface is often a better tool.
Up Next
Getters and setters — methods that look like fields.
Getters & Setters →