Design Pattern

Liskov Substitution Principle (LSP)

Clean Java-only production-ready implementation.


Core Idea

Subtypes must be substitutable for their base type without altering the correctness of the program.

The Violation

A Vehicle base class assumes refuel(). ElectricScooter inherits it but cannot satisfy the contract.

LSP Rules for Interviews

RuleMeaningViolation Example
Preconditions cannot be strongerChild method accepts narrower input than parentParent accepts any ID, child only accepts UUID format
Postconditions cannot be weakerChild returns weaker guarantee than parentParent returns non-null, child returns null
Exceptions cannot be broaderChild throws more exception typesParent throws IOException, child throws Exception
Invariants must be preservedBase class constraints hold in subtypeBase ensures speed >= 0, child returns negative speed

Interview Checklist

  1. Start with capability interfaces (not deep inheritance)
  2. Never throw UnsupportedOperationException in a subtype โ€” that's the #1 LSP signal
  3. Prefer composition: Car has-an Engine instead of Car extends Vehicle with no-op methods
  4. If you see "is-a" that breaks a base assumption, extract focused interfaces

Where This Appears in LLD Problems

ProblemAvoid ThisDo This Instead
Parking Lot (#1)Vehicle with refuel() โ€” e-scooter can'tStartable, Refuelable, Rechargeable
Chess (#9)ChessPiece with enPassant() โ€” king can'tPromotable, EnPassantCapable interfaces
Vending Machine (#7)Item with cook() โ€” cold drinks can'tCookable, Refrigeratable capability interfaces
File System (#20)FileSystemNode with read() โ€” directory can'tReadable for files, Listable for directories

The Fix

Split capabilities into focused interfaces. ElectricScooter implements Rechargeable instead of forcing a no-op refuel().

// โ”€โ”€โ”€ EXAMPLE 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// WHAT WE ARE IMPLEMENTING:
// An autonomous vehicle simulator ensuring all subclasses behave safely when
// substituted for parent interfaces.
//
// WHERE THE PRINCIPLE FITS IN:
// The code demonstrates that substituting ElectricCar for Car behaves
// correctly without breaking core interfaces, whereas violations like ToyCar
// throwing exceptions break expectations.
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// --- Bad: LSP violation ---
abstract class Vehicle {
    abstract void start();
    abstract void refuel();  // Violation: not all vehicles refuel
}

class ElectricScooter extends Vehicle {
    void start() { System.out.println("  Scooter started silently"); }
    void refuel() { throw new UnsupportedOperationException("Scooters don't refuel"); }
    // ^ LSP violation: caller can't substitute Scooter where Vehicle is expected
}

// --- Good: LSP-safe design with capability interfaces ---
interface Startable { void start(); }
interface Refuelable { void refuel(); }
interface Rechargeable { void charge(); }

class PetrolCar implements Startable, Refuelable {
    public void start() { System.out.println("  [Car] Engine started"); }
    public void refuel() { System.out.println("  [Car] Tank filled"); }
}

class ElectricScooterFixed implements Startable, Rechargeable {
    public void start() { System.out.println("  [Scooter] Motor engaged silently"); }
    public void charge() { System.out.println("  [Scooter] Battery charging at 50kW"); }
}

// --- Client that works with any Startable ---
class GarageService {
    public void service(Startable vehicle) {
        System.out.println("  Starting service...");
        vehicle.start();
        // refuel/charge is not assumed โ€” it's checked by capability
        if (vehicle instanceof Refuelable r) {
            r.refuel();
        } else if (vehicle instanceof Rechargeable c) {
            c.charge();
        }
        System.out.println("  Service complete");
    }
}

public class Main {
    public static void main(String[] args) {
        GarageService garage = new GarageService();

        Startable car = new PetrolCar();
        Startable scooter = new ElectricScooterFixed();

        garage.service(car);      // refuels
        garage.service(scooter);  // charges โ€” no crash!
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...