Abstract Classes & Implementing Interfaces

intermediate classes oop abstract interfaces

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.