Let's talk about the Liskov substitution principle. Liskov substitution principle is the L in the SOLID object-oriented design principles. In fact, the principle is actually really really simple.

So the principle states that if you have a base type and you have a subtype, in other words, if you have a base class and you have a class inheriting from that base class then the subclass should be substitutable for the base class at any point in the program. So, in every place where you're using the base type, you should be able to swap that instance for an instance of the subtype without causing any unwanted behavior in the program. It just means that as soon as you are inheriting from the base class in a way that makes the subclass not substitutable for this particular base class then you're breaking the Liskov substitution principle.

To discuss this topic in more details we will talk about such terms as Preconditions, Postconditions, and Invariants. The simplified definition for preconditions is that they are sort of the entry conditions for the method. Whatever is stated in the preconditions must be true. Postconditions are sort of on the other end. The postcondition is whatever must be true when the method has been called and when all statements from the method were executed. And invariants are what has to be true at all times of the method execution.

So here is how it relates to the Liskov Substitution Principle. Preconditions in the subtype must be the same or weaker. Postconditions and invariants in the subtype must be the same or stronger than in the base types.

Let's take a simple example of violating the LSP. Let's say you have a "Vehicle" class which is a base class, and you inherit it in classes "Bicycle" and "Car". That's what you were forced to do because of some bad design. So let's say that the Vehicle base class states that the class needs to have or that the object needs to have a method called "startEngine". This method is supposed to mutate the "engineStarted" property of the concrete Vehicle. But for some odd reason, we really needed Bicycle class to inherit directly from the Vehicle class. But because the Bicycle class doesn't implement the "startEngine" method, what we do is that we throw an exception in the implementation of a "startEngine" method in the Bicycle class.
class Vehicle {
    private engineStarted = false;

    public startEngine() {
        this.engineStarted = true;
    }
}

class Car extends Vehincle {}

class Bicycle extends Vehicle {

    public startEngine() { //we override the base startEngine
        throw new Error('can not start the engine'); // and we violate the LSP
    }

}
So we throw an exception in the "startEngine" method in the subtype "Bicycle" and this is the problem. This means that the subtype is not entirely substitutable. The Bicycle subtype cannot be used in all places where Vehicle class can be used, because it may throw an exception. That's unwanted of course because everything becomes different if that exception is a part of the intent of the design. That was an example of violating the LSP both in terms of invariants and postconditions.

It is helpful to think about how the robustness principle relates to LSP. The robustness principle says: you have to be liberal in what you take and be strict in what you give away. This means that the subclass or a method in the subclass must receive everything that the base class is expecting it to be able to receive. The whole set of possible values. But because it's liberal, it may also accept a bit more. The postconditions of the subclass, however, must be the same or stronger. So methods in the subclass must return either the same types of values that you get from the base class, or they must return a subset of the set of possible values. So you would never receive anything unexpected from the subclass. It's all about being able to depend on the subclass in the contract. And Invariants lastly. Invariants of the base class must be followed or must be stronger in the subclass. Thinking about this in terms of sets again, the set of possible states of the subclass must either be the same as the set of the base class or it must be stricter. It must be a subset of the set of states of the base class.

Violations of the LSP are an indicator that your system may be poorly architectured in terms of inheritance and you may be overusing inheritance in places where inheritance shouldn't actually be used.