Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleFlipkartCREDRazorpay

Liskov Substitution Principle - Subtype Behavioural Contract

Choose your preparation mode3 modes available
🎯
Liskov Substitution Principle - Subtype Behavioural Contract
mediumOOPAmazonGoogleFlipkart

Imagine a payment system where a new payment method subclass breaks the expected behavior of the base payment interface, causing unexpected failures in the checkout process.

💡 Beginners often confuse LSP with just inheritance or method overriding, missing the subtlety that subtype objects must behave consistently with their base types to avoid breaking client code.
📋
Interview Question

Explain the Liskov Substitution Principle (LSP) and its importance as a behavioural contract for subtypes in object-oriented design. How does violating LSP affect software design?

Subtype substitutabilityBehavioural contracts in inheritanceCovariance and contravariance in method signatures
💡
Scenario & Trace
ScenarioA Rectangle class with width and height setters is extended by a Square subclass that overrides setters to keep width and height equal.
Client code expects to set width and height independently on a Rectangle object. When a Square instance is substituted, setting width changes height unexpectedly, violating client expectations and breaking the program logic.
ScenarioA Bird base class has a fly() method. A Penguin subclass inherits Bird but cannot fly.
Client code calls fly() on Bird references. Substituting a Penguin instance leads to unexpected behavior or exceptions, violating the behavioural contract that all Birds can fly.
  • Subtype narrows the range of acceptable input parameters → what happens to client code?
  • Subtype strengthens postconditions or invariants → how does this affect substitutability?
  • Covariant return types in overridden methods → when is this allowed or problematic?
⚠️
Common Mistakes
Confusing LSP with simple inheritance or polymorphism

Interviewer thinks candidate lacks understanding of behavioural contracts and substitutability

✅ Emphasize that LSP is about preserving expected behavior, not just code reuse

Ignoring method preconditions and postconditions in subtype overrides

Candidate misses how contract violations cause runtime errors or logic bugs

✅ Learn to analyze method contracts and ensure subtypes do not strengthen preconditions or weaken postconditions

Assuming overriding methods can arbitrarily change parameter types

Interviewer suspects candidate does not understand covariance and contravariance rules

✅ Understand that parameter types must be contravariant or unchanged to maintain substitutability

Believing LSP only applies to method signatures, ignoring state invariants

Candidate misses that state changes can break substitutability even if signatures match

✅ Recognize that subtypes must maintain all invariants of the base type to be substitutable

🧠
Basic Definition - What It Is
💡 This level covers the fundamental idea that subtypes must be replaceable for their base types without altering program correctness.

Intuition

A subtype should behave like its parent type so that clients using the parent type can use the subtype without surprises.

Explanation

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. This means the subclass must honor the behavioural expectations set by the superclass. Violating this principle leads to fragile code where substituting a subtype breaks client code, causing bugs and maintenance headaches.

Memory Hook

💡 Think of a square peg fitting into a round hole: if it doesn’t fit as expected, it breaks the system.

Interview Questions

❓ What does it mean if a subclass violates LSP?
  • Subclass objects cannot be used in place of superclass objects without errors
  • Client code relying on superclass behavior breaks
⏱
Depth Level
Interview Time30 seconds
Depthbasic

Covers the core definition and why it matters; sufficient for quick screening questions.

Interview Target: Minimum floor - never go below this

Knowing only this lets you pass initial interviews but won’t impress deeper technical rounds.

🧠
Mechanism Depth - How It Works
💡 This level explains the behavioural contract details and how method signatures and invariants affect substitutability.

Intuition

LSP enforces that subtype methods must accept the same or broader inputs and guarantee the same or stronger outputs and invariants as the base type.

Explanation

LSP is a behavioural contract that ensures subtypes preserve the expectations set by their supertypes. This involves several rules: preconditions cannot be strengthened (subtypes must accept all inputs the base type accepts), postconditions cannot be weakened (subtypes must fulfill all guarantees of the base type), and invariants must be maintained. Additionally, method signatures must be compatible, allowing covariance in return types but contravariance in parameter types. Violating these rules causes client code to fail or behave unpredictably when using subtype instances.

Memory Hook

💡 Think of a contract where the subtype promises to do everything the base type promises, no less and no more restrictive.

Interview Questions

❓ How do preconditions and postconditions relate to LSP?
  • Subtypes cannot require more restrictive preconditions than supertypes
  • Subtypes must guarantee at least what supertypes guarantee in postconditions
  • Violating these breaks substitutability
⏱
Depth Level
Interview Time2-3 minutes
Depthintermediate

Demonstrates understanding of the behavioural contract and its implications on method design.

Interview Target: Target level for FAANG on-sites

Mastering this level distinguishes you from most candidates and shows deep OOP design knowledge.

📊
Explanation Depth Levels
💡 Choose your explanation depth based on interview stage and role expectations.
LevelInterview TimeSuitable ForRisk
Basic Definition30sScreening call or quick conceptual questionsToo shallow for on-site or design interviews
Mechanism Depth2-3 minutesOn-site interviews, design discussions, FAANG rolesRequires solid understanding; skipping details risks appearing superficial
💼
Interview Strategy
💡 Use this guide to structure your explanation clearly and confidently before interviews.

How to Present

Start with a clear definition of LSPGive a relatable example or analogyExplain the behavioural contract details including pre/postconditionsDiscuss common edge cases and consequences of violations

Time Allocation

Definition: 30s → Example: 1min → Mechanism: 2min → Edge cases: 30s. Total ~4min

What the Interviewer Tests

Your ability to explain substitutability beyond inheritance, understanding of behavioural contracts, and awareness of subtle pitfalls.

Common Follow-ups

  • What happens if a subtype strengthens preconditions? → It breaks substitutability because clients may pass inputs the subtype rejects.
  • Can covariant return types violate LSP? → Generally allowed if they preserve expected behavior, but must be used carefully.
💡 These follow-ups test your grasp of nuanced contract rules and practical implications.
🔍
Pattern Recognition

When to Use

When asked about SOLID principles, inheritance pitfalls, or designing robust class hierarchies.

Signature Phrases

'Explain the Liskov Substitution Principle''What happens when a subtype changes method behavior?''Compare inheritance vs LSP compliance'

NOT This Pattern When

Similar Problems

Practice

(1/5)
1. Trace the sequence of events when a client tries to update a private field via a setter method in an encapsulated class.
easy
A. The client directly modifies the private field without any method call
B. The getter method is called first to check the current value before updating
C. The setter method validates the input, updates the private field, then returns control to the client
D. The private field is copied to a public variable which the client modifies

Solution

  1. Step 1: Identify encapsulation flow

    Private fields cannot be accessed directly; setters provide controlled access.
  2. Step 2: Follow setter method process

    Setter receives input, validates it, updates the private field, then returns control.
  3. Step 3: Analyze incorrect options

    The client directly modifies the private field without any method call violates data hiding. The getter method is called first to check the current value before updating misuses getter before setter. The private field is copied to a public variable which the client modifies breaks encapsulation by exposing private data.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Setters control updates with validation, preserving encapsulation.
Hint: Setters validate and update private data, not direct access [OK]
Common Mistakes:
  • Assuming direct field access through setters
  • Confusing getter and setter roles
  • Thinking private data is copied out for modification
2. Identify the bug in the following decorator implementation that causes incorrect behavior when stacking decorators:
medium
A. Line 5: Recursive call to self.cost() instead of super().cost() causes infinite recursion
B. Line 6: Incorrect string formatting in description
C. Line 2: Incorrect call to super().__init__
D. Line 4: Missing amount parameter default value

Solution

  1. Step 1: Analyze cost() method

    cost() calls self.cost() recursively, causing infinite recursion instead of delegating to wrapped object.
  2. Step 2: Identify correct delegation

    Should call super().cost() to delegate to wrapped coffee object's cost method.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Recursive call causes stack overflow, breaking decorator chain [OK]
Hint: Decorator methods must delegate via super(), not self [OK]
Common Mistakes:
  • Calling self.cost() instead of super().cost() in decorators
  • Forgetting to delegate calls properly
3. Identify the bug in the following code snippet implementing the strategy pattern for payment processing:
class PaymentProcessor:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def pay(self, amount):
        if isinstance(self.strategy, CreditCardStrategy):
            print(f"Processing credit card payment of ${amount}")
        elif isinstance(self.strategy, UPIStrategy):
            print(f"Processing UPI payment of ${amount}")
        else:
            print("Invalid payment method")
medium
A. The bug is that the pay method uses conditional checks instead of polymorphism.
B. The bug is missing a call to the strategy's pay method inside pay().
C. The bug is in the constructor where the strategy is not assigned properly.
D. The bug is that the else clause should raise an exception instead of printing.

Solution

  1. Step 1: Analyze the pay method implementation

    The pay method uses isinstance checks and conditionals to decide behavior, which defeats the purpose of polymorphism.
  2. Step 2: Identify correct polymorphic usage

    The correct approach is to call self.strategy.pay(amount) directly, letting each strategy handle its own payment logic.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Using conditionals in pay() breaks the strategy pattern [OK]
Hint: Strategy pattern requires polymorphic calls, not conditionals [OK]
Common Mistakes:
  • Using type checks instead of polymorphism
4. If the parking lot system needs to support dynamic pricing based on spot location, time, and vehicle type, which design approach best supports this extensibility without modifying existing classes?
hard
A. Use the Strategy pattern to encapsulate different pricing algorithms and assign them dynamically
B. Add pricing logic directly inside the ParkingSpot class with multiple if-else conditions
C. Hardcode pricing rules in the ParkingLot class for all spot and vehicle combinations
D. Create separate subclasses of ParkingSpot for each pricing rule variant

Solution

  1. Step 1: Identify extensibility needs

    Pricing rules vary by multiple factors and may change frequently.
  2. Step 2: Evaluate design options

    Embedding logic in ParkingSpot or ParkingLot leads to rigid, hard-to-maintain code. Subclass explosion is unmanageable.
  3. Step 3: Recognize Strategy pattern benefits

    Strategy encapsulates pricing algorithms, allowing dynamic assignment and easy extension without modifying existing classes.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Strategy pattern supports flexible, maintainable pricing logic [OK]
Hint: Use Strategy pattern to keep pricing logic flexible and decoupled [OK]
Common Mistakes:
  • Embedding complex logic inside core classes
  • Using inheritance for every pricing variant
5. Suppose you want to extend the observer pattern to allow observers to receive multiple notifications for the same event type without unsubscribing (i.e., observers can be registered multiple times for the same event). Which modification to the thread-safe observer pattern implementation below correctly supports this requirement without breaking thread safety or notification correctness? Options: A) Change the observers data structure from a set to a list per event type and allow duplicates. B) Keep using a set but add a counter for each observer to track multiple subscriptions. C) Use a dictionary mapping observers to their subscription counts per event type. D) Use a queue per event type to enqueue notifications and process them asynchronously.
hard
A. Keep using a set but add a counter for each observer to track multiple subscriptions.
B. Change the observers data structure from a set to a list per event type and allow duplicates.
C. Use a dictionary mapping observers to their subscription counts per event type.
D. Use a queue per event type to enqueue notifications and process them asynchronously.

Solution

  1. Step 1: Understand the requirement

    Observers can subscribe multiple times to the same event type and should receive multiple notifications accordingly.
  2. Step 2: Evaluate data structure changes

    Using a set alone disallows duplicates. A list allows duplicates but is not thread-safe and inefficient for removals. A dictionary mapping observers to counts tracks multiple subscriptions safely.
  3. Step 3: Choose the best approach

    Adding a counter per observer in the set (Keep using a set but add a counter for each observer to track multiple subscriptions.) or using a dictionary (Use a dictionary mapping observers to their subscription counts per event type.) can work, but adding a counter per observer in the set is simpler and keeps thread safety with minimal changes.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Counting subscriptions per observer preserves multiple notifications safely [OK]
Hint: Track subscription counts to allow multiple notifications [OK]
Common Mistakes:
  • Using list causes concurrency issues and inefficient removals
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard