In simple language, an abstract class is a half-finished class. It can have real methods AND placeholder methods that subclasses must implement. We can’t new it directly — only its subclasses.
abstract class Shape {
abstract area(): number; // must be implemented by subclass
describe(): string { // real method — subclasses inherit
return `area is ${this.area()}`;
}
}
class Circle extends Shape {
constructor(private r: number) { super(); }
area() { return Math.PI * this.r * this.r; }
}
const c = new Circle(5);
new Shape(); // Error — cannot create instance of abstract class
Abstract vs interface — when to use which
The only difference, basically:
- Interface: pure contract, no implementation, zero runtime presence.
- Abstract class: contract + shared implementation + runtime presence (you can have a constructor, fields, methods).
If we only need to describe a shape, interface. If we need shared code AND a contract, abstract class.
// shape contract only
interface Greeter {
greet(): string;
}
// contract + shared logic
abstract class BaseGreeter {
abstract name: string;
greet() { return `Hi, ${this.name}`; }
}
implements — promising to match an interface
A class can promise it satisfies one or more interfaces. The compiler then verifies the shape matches.
interface Loggable {
log(): void;
}
interface Serializable {
toJSON(): string;
}
class User implements Loggable, Serializable {
constructor(public name: string) {}
log() { console.log(this.name); }
toJSON() { return JSON.stringify({ name: this.name }); }
}
Important: implements is just a check. It does NOT change the class — it only verifies. So adding implements Loggable doesn’t give us a log method; we still have to write it.
extends vs implements
extends: inherit from ONE class (gets fields, methods, prototype chain).implements: satisfy any number of interfaces (gets nothing, just checked).
A class can extends one class and implements multiple interfaces at the same time. Think of it like the only difference is what we get back — inheritance gives code, implementation gives a check.
class Admin extends User implements Loggable, Serializable {
// ...
}
Abstract + protected combo
A common pattern: keep abstract methods protected so only subclasses (and the abstract class itself) can call them, while exposing a public template method.
abstract class Pipeline {
run() {
this.before();
this.step(); // subclass fills this in
this.after();
}
protected before() {}
protected after() {}
protected abstract step(): void;
}
class Etl extends Pipeline {
protected step() { console.log("extracting..."); }
}
This is the Template Method pattern — common interview question.
Why abstract classes still matter
Even with interfaces being so powerful, abstract classes are great when:
- We need shared state (fields, not just methods).
- We have a common algorithm with customizable steps.
- We want to enforce constructor logic for all subclasses.
If none of those apply, interface is lighter and works just as well.