Liskov Substitution Principle

What problem is this solving?

Some inheritance hierarchies look correct but break when a child class is used through the parent type.

If a child class overrides a method by throwing "not supported", the parent contract was probably wrong.

LSP helps you design parent and child types that behave consistently.


Simple definition

The Liskov Substitution Principle says:

If a program expects a parent type, it should work correctly with any child type.

This is the L in SOLID.


Understand each word

Liskov

The principle is named after Barbara Liskov, who described this substitution idea.

You do not need the formal mathematical definition for most interviews.

You need the practical meaning.

Substitution

Substitution means replacement.

If code accepts a parent class or interface, you should be able to pass a child implementation without breaking behavior.

Principle

The guideline is:

Child classes should not surprise code that works with the parent type.


Bad/naive example

Suppose we model birds like this:

class Bird {
    void fly() {
        System.out.println("Flying");
    }
}

class Sparrow extends Bird {
}

class Penguin extends Bird {
    @Override
    void fly() {
        throw new UnsupportedOperationException("Penguins cannot fly");
    }
}

Now some code uses Bird:

class BirdTrainer {
    void makeBirdFly(Bird bird) {
        bird.fly();
    }
}

This works for Sparrow.

But it breaks for Penguin.

trainer.makeBirdFly(new Penguin()); // runtime error

The child class cannot safely replace the parent.

That violates LSP.


Better explanation

Do not put fly() in Bird if all birds cannot fly.

Model the capability separately:

class Bird {
}

interface Flyable {
    void fly();
}

class Sparrow extends Bird implements Flyable {
    public void fly() {
        System.out.println("Sparrow flying");
    }
}

class Penguin extends Bird {
}

Now the trainer only accepts birds that can fly:

class BirdTrainer {
    void makeBirdFly(Flyable bird) {
        bird.fly();
    }
}

Penguin is no longer forced into a fake flying behavior.


Real example

Suppose we have:

interface FileStorage {
    void upload(File file);
    File download(String fileName);
}

This implementation is fine:

class S3Storage implements FileStorage {
    public void upload(File file) {}
    public File download(String fileName) { return new File(fileName); }
}

But this is suspicious:

class WriteOnlyStorage implements FileStorage {
    public void upload(File file) {}

    public File download(String fileName) {
        throw new UnsupportedOperationException("Download not supported");
    }
}

If FileStorage promises upload and download, every implementation should support both.

Otherwise, code using FileStorage can break.

Better:

interface FileUploader {
    void upload(File file);
}

interface FileDownloader {
    File download(String fileName);
}

Now a write-only storage only implements FileUploader.


Common mistake

Do not use inheritance only because two classes share a few fields.

Use inheritance only when the child can fully honor the behavior promised by the parent.


Interview Answer

If an interviewer asks:

What is Liskov Substitution Principle?

You can answer:

Liskov Substitution Principle means a child class should be usable wherever its parent type is expected without breaking correctness. A subtype should honor the behavior promised by the parent. For example, if Bird has fly(), then Penguin extends Bird but throws an exception in fly() breaks LSP. A better design is to keep Bird separate and create a Flyable interface only for birds that can fly.