Last updated: Apr 29, 2026

Liskov Substitution

Subclasses must work wherever the base class works. If your code uses a parent class, it should be able to use any subclass without knowing which one it is. A subclass can add new behavior, but it cannot remove or break behavior the parent promised.

Two red flags that you are violating LSP:

  • A subclass throws an exception for a method the parent provides
  • Callers need instanceof checks to handle specific subclasses

Example

Penguin extends Bird but breaks the expectation that all birds can fly:

class Bird {
  fly(): void {
    // flying logic
  }
}

class Penguin extends Bird {
  fly(): void {
    throw new Error("Penguins can't fly");
  }
}

Split the flying behavior into its own interface so only birds that can actually fly implement it:

abstract class Bird {
  abstract eat(): void;
}

abstract class FlyingBird extends Bird {
  abstract fly(): void;
}

class Sparrow extends FlyingBird {
  eat(): void {}
  fly(): void {}
}

class Penguin extends Bird {
  eat(): void {}
}

Now any code that works with Bird can accept a Penguin without breaking. Code that specifically needs a flying bird uses FlyingBird.