A Algo3DVerse
L — Liskov Substitution
SOLID — Principle 3 of 5

Liskov Substitution
Principle

Objects of a subtype must be substitutable for objects of their supertype without altering the correctness of the program.

Barbara Liskov's 1987 principle: if S is a subtype of T, then objects of type T may be replaced with objects of type S without breaking the program. In plain terms — a subclass must honour the contract of its parent class, not just inherit its interface.

Substitution in 3D

The glowing pedestal accepts any shape. Press Swap In to replace the parent with a subtype. Press Violation to see what happens when the contract breaks.

drag — scroll to zoom

Code example

The classic Rectangle / Square trap and how to fix it. Toggle between C# and JavaScript.

LSP VIOLATION ? FIX
// ? BAD — Square breaks Rectangle's area contract
public class Rectangle
{
    public virtual int Width  { get; set; }
    public virtual int Height { get; set; }
    public int Area() => Width * Height;
}

public class Square : Rectangle  // LSP violation!
{
    public override int Width  { set { base.Width = base.Height = value; } }
    public override int Height { set { base.Height = base.Width  = value; } }
}

// This breaks when Square is substituted for Rectangle:
Rectangle r = new Square();
r.Width = 4; r.Height = 5;
// Expected: 4—5=20. Actual: 5—5=25. Contract violated!

// ? GOOD — both implement the same interface independently
public interface IShape
{
    int Area();
}

public class Rectangle : IShape
{
    public int Width { get; }
    public int Height { get; }
    public Rectangle(int w, int h) { Width = w; Height = h; }
    public int Area() => Width * Height;  // w — h
}

public class Square : IShape
{
    public int Side { get; }
    public Square(int s) { Side = s; }
    public int Area() => Side * Side;    // s—
}
// ? BAD — Square inherits Rectangle and breaks area
class Rectangle {
    constructor(w, h) { this.width = w; this.height = h; }
    setWidth(w)  { this.width  = w; }
    setHeight(h) { this.height = h; }
    area() { return this.width * this.height; }
}

class Square extends Rectangle { // LSP violation!
    setWidth(s)  { this.width = this.height = s; }
    setHeight(s) { this.width = this.height = s; }
}

const r = new Square(0, 0);
r.setWidth(4); r.setHeight(5);
// Expected: 4—5=20. Actual: 5—5=25 ? contract broken!

// ? GOOD — separate classes, common interface (duck typing)
class Rectangle {
    constructor(w, h) { this.width = w; this.height = h; }
    area() { return this.width * this.height; }
}

class Square {
    constructor(s) { this.side = s; }
    area() { return this.side ** 2; }
}

// Both satisfy the IShape contract: .area() works correctly
function printArea(shape) { console.log(shape.area()); }
printArea(new Rectangle(4, 5)); // 20
printArea(new Square(5));       // 25

Key concepts

🔄

Substitutability

swap — transparent

Wherever a base type is expected, a subtype can be used without the caller needing to know — or care — which concrete type it is.

📋

Contract

preconditions — postconditions

A class has a contract: preconditions, postconditions, and invariants. Subtypes must honour all of them, not just the method signatures.

🔲

The Square Problem

classic trap — famous example

Mathematically a Square is a Rectangle. But in code, Square overriding Rectangle's setters breaks the width—height invariant. Inheritance ? is-a.

🧬

Behavioural Subtyping

behaviour — not just type

LSP is about behaviour, not syntax. A subtype that compiles but changes expected outputs is just as much a violation as a type error.

→ Open / Closed SOLID Principles — Algo3DVerse Interface Segregation ?
Press ? to begin the narration walkthrough.
0 / 8