Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartRazorpaySwiggyPhonePe

Strategy Pattern - Replace Conditionals with Polymorphism

Choose your preparation mode3 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
🎯
Strategy Pattern - Replace Conditionals with Polymorphism
mediumOOPAmazonGoogleMicrosoft

Imagine a payment system that supports multiple payment methods, each with its own processing logic. Instead of cluttering the code with many if-else conditions, how can we design it to be clean, extensible, and maintainable?

💡 This problem is about designing software that avoids complex conditional logic by using polymorphism. Beginners often struggle because they write many if-else statements, which become hard to maintain and extend. Understanding how to replace conditionals with design patterns like Strategy is key to writing scalable OOP code.
📋
Problem Statement

Given a context that requires selecting one of several algorithms or behaviors at runtime (e.g., different payment methods), design a system that replaces conditional statements with polymorphic classes implementing a common interface. The system should allow adding new strategies without modifying existing code.

The number of strategies can vary and may increase over timeThe context must be able to switch strategies at runtimeAvoid using if-else or switch-case statements to select behavior
💡
Example
Input"User selects 'CreditCard' payment method and pays $100"
OutputProcessing credit card payment of $100

The system uses the CreditCardStrategy class to process the payment, avoiding conditional checks.

Input"User selects 'UPI' payment method and pays $50"
OutputProcessing UPI payment of $50

The system uses the UPIStrategy class polymorphically to handle payment.

  • No payment strategy selected → should handle gracefully or throw error
  • Adding a new payment method without changing existing code → system should support easily
  • Switching payment strategy multiple times at runtime → context should update behavior accordingly
  • Strategy implementation throws an exception → system should handle errors properly
⚠️
Common Mistakes
Using if-else chains instead of polymorphism

Code becomes hard to maintain and extend; adding new behaviors requires modifying existing code

Refactor to use Strategy pattern with interfaces and separate classes

Not defining a common interface for strategies

Context cannot treat strategies uniformly, leading to duplicated code or type errors

Define an abstract base class or interface that all strategies implement

Hardcoding strategy selection inside the context

Context becomes tightly coupled to specific strategies, reducing flexibility

Use dependency injection or factories to provide strategies externally

Not handling invalid or unsupported strategies

Runtime errors or unexpected behavior when an unknown strategy is requested

Add error handling in factory or context to manage invalid inputs gracefully

Changing strategy by modifying code instead of runtime switching

Requires code changes and recompilation to change behavior, defeating the pattern's purpose

Provide methods to set or change strategy objects at runtime

🧠
Brute Force (Using Conditional Statements)
💡 This approach shows the naive way beginners implement multiple behaviors using if-else or switch-case statements. It helps understand why this approach becomes problematic as the number of behaviors grows.

Intuition

Use explicit conditional checks to select and execute the appropriate behavior based on input or state.

Algorithm

  1. Define a method that takes input indicating which behavior to execute
  2. Use if-else or switch-case to select the correct behavior
  3. Execute the selected behavior inline
  4. Return or process the result
💡 This approach is easy to write but hard to maintain or extend because all logic is centralized and tightly coupled.
</>
Code
class PaymentProcessor:
    def pay(self, method, amount):
        if method == 'CreditCard':
            print(f"Processing credit card payment of ${amount}")
        elif method == 'UPI':
            print(f"Processing UPI payment of ${amount}")
        elif method == 'NetBanking':
            print(f"Processing net banking payment of ${amount}")
        else:
            print("Invalid payment method")

# Driver code
if __name__ == '__main__':
    processor = PaymentProcessor()
    processor.pay('CreditCard', 100)
    processor.pay('UPI', 50)
    processor.pay('Cash', 20)
Line Notes
def pay(self, method, amount):Defines a single method handling all payment types, leading to conditional complexity
if method == 'CreditCard':Checks for one specific payment method explicitly
elif method == 'UPI':Checks for another payment method, adding to conditional chain
else:Handles invalid or unsupported payment methods explicitly
public class PaymentProcessor {
    public void pay(String method, double amount) {
        if (method.equals("CreditCard")) {
            System.out.println("Processing credit card payment of $" + amount);
        } else if (method.equals("UPI")) {
            System.out.println("Processing UPI payment of $" + amount);
        } else if (method.equals("NetBanking")) {
            System.out.println("Processing net banking payment of $" + amount);
        } else {
            System.out.println("Invalid payment method");
        }
    }

    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentProcessor();
        processor.pay("CreditCard", 100);
        processor.pay("UPI", 50);
        processor.pay("Cash", 20);
    }
}
Line Notes
public void pay(String method, double amount) {Single method handling all payment types with conditionals
if (method.equals("CreditCard")) {Explicit check for CreditCard payment
} else if (method.equals("UPI")) {Additional conditional branch for UPI
} else {Fallback for unsupported payment methods
}Closes the PaymentProcessor class
#include <iostream>
#include <string>
using namespace std;

class PaymentProcessor {
public:
    void pay(const string& method, double amount) {
        if (method == "CreditCard") {
            cout << "Processing credit card payment of $" << amount << endl;
        } else if (method == "UPI") {
            cout << "Processing UPI payment of $" << amount << endl;
        } else if (method == "NetBanking") {
            cout << "Processing net banking payment of $" << amount << endl;
        } else {
            cout << "Invalid payment method" << endl;
        }
    }
};

int main() {
    PaymentProcessor processor;
    processor.pay("CreditCard", 100);
    processor.pay("UPI", 50);
    processor.pay("Cash", 20);
    return 0;
}
Line Notes
void pay(const string& method, double amount) {Method handles all payment types with conditionals inside
if (method == "CreditCard") {Checks for CreditCard payment explicitly
} else if (method == "UPI") {Checks for UPI payment explicitly
} else {Handles invalid payment methods explicitly
}Closes the PaymentProcessor class
class PaymentProcessor {
    pay(method, amount) {
        if (method === 'CreditCard') {
            console.log(`Processing credit card payment of $${amount}`);
        } else if (method === 'UPI') {
            console.log(`Processing UPI payment of $${amount}`);
        } else if (method === 'NetBanking') {
            console.log(`Processing net banking payment of $${amount}`);
        } else {
            console.log('Invalid payment method');
        }
    }
}

// Driver code
const processor = new PaymentProcessor();
processor.pay('CreditCard', 100);
processor.pay('UPI', 50);
processor.pay('Cash', 20);
Line Notes
pay(method, amount) {Single method with conditional logic for all payment types
if (method === 'CreditCard') {Explicit conditional branch for CreditCard
} else if (method === 'UPI') {Additional conditional branch for UPI
} else {Fallback for unsupported payment methods
}Closes the PaymentProcessor class
Complexity
TimeO(1)
SpaceO(1)

Each payment call executes a fixed number of conditional checks, so time and space are constant.

💡 Even though this is fast for a few payment methods, adding more methods increases the number of conditionals, making the code harder to read and maintain.
Interview Verdict: Accepted but not scalable

This approach works but quickly becomes unmanageable as the number of strategies grows, motivating the need for better design.

🧠
Better (Using Strategy Pattern with Interface and Concrete Classes)
💡 This approach introduces polymorphism by defining a common interface and separate classes for each strategy, eliminating conditionals and improving extensibility.

Intuition

Encapsulate each behavior in its own class implementing a common interface, and let the context delegate calls to the selected strategy object.

Algorithm

  1. Define a Strategy interface with a common method (e.g., pay(amount))
  2. Implement concrete Strategy classes for each behavior (CreditCardStrategy, UPIStrategy, etc.)
  3. Create a Context class that holds a reference to a Strategy
  4. Context delegates the behavior call to the current Strategy object
💡 This design separates concerns and allows adding new strategies without modifying existing code.
</>
Code
from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class CreditCardStrategy(PaymentStrategy):
    def pay(self, amount):
        print(f"Processing credit card payment of ${amount}")

class UPIStrategy(PaymentStrategy):
    def pay(self, amount):
        print(f"Processing UPI payment of ${amount}")

class PaymentProcessor:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def pay(self, amount):
        self.strategy.pay(amount)

# Driver code
if __name__ == '__main__':
    processor = PaymentProcessor(CreditCardStrategy())
    processor.pay(100)
    processor.strategy = UPIStrategy()
    processor.pay(50)
Line Notes
class PaymentStrategy(ABC):Defines an abstract base class to enforce a common interface
@abstractmethodEnsures subclasses implement the pay method
def __init__(self, strategy: PaymentStrategy):Context holds a reference to a strategy object
self.strategy.pay(amount)Delegates payment processing to the current strategy
interface PaymentStrategy {
    void pay(double amount);
}

class CreditCardStrategy implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

class UPIStrategy implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("Processing UPI payment of $" + amount);
    }
}

class PaymentProcessor {
    private PaymentStrategy strategy;

    public PaymentProcessor(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void pay(double amount) {
        strategy.pay(amount);
    }

    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentProcessor(new CreditCardStrategy());
        processor.pay(100);
        processor.setStrategy(new UPIStrategy());
        processor.pay(50);
    }
}
Line Notes
interface PaymentStrategy {Defines the common interface for all payment strategies
class CreditCardStrategy implements PaymentStrategy {Concrete strategy implementing the interface
private PaymentStrategy strategy;Context holds a reference to the current strategy
strategy.pay(amount);Delegates payment processing to the strategy object
}Closes the PaymentProcessor class
#include <iostream>
#include <memory>
using namespace std;

class PaymentStrategy {
public:
    virtual void pay(double amount) = 0;
    virtual ~PaymentStrategy() {}
};

class CreditCardStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "Processing credit card payment of $" << amount << endl;
    }
};

class UPIStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "Processing UPI payment of $" << amount << endl;
    }
};

class PaymentProcessor {
    unique_ptr<PaymentStrategy> strategy;
public:
    PaymentProcessor(unique_ptr<PaymentStrategy> strat) : strategy(move(strat)) {}
    void setStrategy(unique_ptr<PaymentStrategy> strat) {
        strategy = move(strat);
    }
    void pay(double amount) {
        strategy->pay(amount);
    }
};

int main() {
    PaymentProcessor processor(make_unique<CreditCardStrategy>());
    processor.pay(100);
    processor.setStrategy(make_unique<UPIStrategy>());
    processor.pay(50);
    return 0;
}
Line Notes
class PaymentStrategy {Abstract base class defining the interface
virtual void pay(double amount) = 0;Pure virtual function to enforce implementation
unique_ptr<PaymentStrategy> strategy;Context holds ownership of strategy object
strategy->pay(amount);Delegates payment processing to strategy
}Closes the PaymentProcessor class
class PaymentStrategy {
    pay(amount) {
        throw new Error('pay() must be implemented');
    }
}

class CreditCardStrategy extends PaymentStrategy {
    pay(amount) {
        console.log(`Processing credit card payment of $${amount}`);
    }
}

class UPIStrategy extends PaymentStrategy {
    pay(amount) {
        console.log(`Processing UPI payment of $${amount}`);
    }
}

class PaymentProcessor {
    constructor(strategy) {
        this.strategy = strategy;
    }
    setStrategy(strategy) {
        this.strategy = strategy;
    }
    pay(amount) {
        this.strategy.pay(amount);
    }
}

// Driver code
const processor = new PaymentProcessor(new CreditCardStrategy());
processor.pay(100);
processor.setStrategy(new UPIStrategy());
processor.pay(50);
Line Notes
class PaymentStrategy {Defines base class with unimplemented pay method
pay(amount) { throw new Error('pay() must be implemented'); }Enforces subclasses to implement pay
constructor(strategy) {Context stores current strategy object
this.strategy.pay(amount);Delegates payment processing to strategy
}Closes the PaymentProcessor class
Complexity
TimeO(1)
SpaceO(1)

Each payment call delegates to a strategy object with constant time and space overhead.

💡 This design adds a small overhead of object creation but greatly improves maintainability and extensibility.
Interview Verdict: Accepted and recommended

This approach is the canonical way to replace conditionals with polymorphism and is highly valued in interviews.

🧠
Optimal (Using Dependency Injection and Runtime Strategy Selection)
💡 This approach extends the Strategy pattern by allowing the context to select or change strategies dynamically at runtime, often using dependency injection or factory methods.

Intuition

Decouple strategy creation from usage by injecting the desired strategy into the context, enabling flexible runtime behavior changes without modifying context code.

Algorithm

  1. Define a Strategy interface and concrete implementations as before
  2. Use a factory or dependency injection to provide the desired strategy to the context
  3. Allow the context to switch strategies at runtime by setting a new strategy object
  4. Invoke the strategy's method via the context to perform the behavior
💡 This approach maximizes flexibility and testability by separating strategy selection from usage.
</>
Code
from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class CreditCardStrategy(PaymentStrategy):
    def pay(self, amount):
        print(f"Processing credit card payment of ${amount}")

class UPIStrategy(PaymentStrategy):
    def pay(self, amount):
        print(f"Processing UPI payment of ${amount}")

class PaymentStrategyFactory:
    @staticmethod
    def get_strategy(method):
        if method == 'CreditCard':
            return CreditCardStrategy()
        elif method == 'UPI':
            return UPIStrategy()
        else:
            raise ValueError('Invalid payment method')

class PaymentProcessor:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def set_strategy(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def pay(self, amount):
        self.strategy.pay(amount)

# Driver code
if __name__ == '__main__':
    method = 'CreditCard'
    strategy = PaymentStrategyFactory.get_strategy(method)
    processor = PaymentProcessor(strategy)
    processor.pay(100)

    # Switch strategy at runtime
    method = 'UPI'
    processor.set_strategy(PaymentStrategyFactory.get_strategy(method))
    processor.pay(50)
Line Notes
class PaymentStrategyFactory:Factory encapsulates strategy creation logic
def get_strategy(method):Selects and returns appropriate strategy instance based on input
def set_strategy(self, strategy: PaymentStrategy):Allows changing strategy at runtime
self.strategy.pay(amount)Delegates payment processing to current strategy
interface PaymentStrategy {
    void pay(double amount);
}

class CreditCardStrategy implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

class UPIStrategy implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("Processing UPI payment of $" + amount);
    }
}

class PaymentStrategyFactory {
    public static PaymentStrategy getStrategy(String method) {
        switch (method) {
            case "CreditCard": return new CreditCardStrategy();
            case "UPI": return new UPIStrategy();
            default: throw new IllegalArgumentException("Invalid payment method");
        }
    }
}

class PaymentProcessor {
    private PaymentStrategy strategy;

    public PaymentProcessor(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void pay(double amount) {
        strategy.pay(amount);
    }

    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentProcessor(PaymentStrategyFactory.getStrategy("CreditCard"));
        processor.pay(100);
        processor.setStrategy(PaymentStrategyFactory.getStrategy("UPI"));
        processor.pay(50);
    }
}
Line Notes
class PaymentStrategyFactory {Factory class centralizes strategy instantiation
public static PaymentStrategy getStrategy(String method) {Returns appropriate strategy based on input string
public void setStrategy(PaymentStrategy strategy) {Allows context to change strategy dynamically
strategy.pay(amount);Delegates payment processing to current strategy
}Closes the PaymentProcessor class
#include <iostream>
#include <memory>
#include <string>
#include <stdexcept>
using namespace std;

class PaymentStrategy {
public:
    virtual void pay(double amount) = 0;
    virtual ~PaymentStrategy() {}
};

class CreditCardStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "Processing credit card payment of $" << amount << endl;
    }
};

class UPIStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "Processing UPI payment of $" << amount << endl;
    }
};

class PaymentStrategyFactory {
public:
    static unique_ptr<PaymentStrategy> getStrategy(const string& method) {
        if (method == "CreditCard") {
            return make_unique<CreditCardStrategy>();
        } else if (method == "UPI") {
            return make_unique<UPIStrategy>();
        } else {
            throw invalid_argument("Invalid payment method");
        }
    }
};

class PaymentProcessor {
    unique_ptr<PaymentStrategy> strategy;
public:
    PaymentProcessor(unique_ptr<PaymentStrategy> strat) : strategy(move(strat)) {}
    void setStrategy(unique_ptr<PaymentStrategy> strat) {
        strategy = move(strat);
    }
    void pay(double amount) {
        strategy->pay(amount);
    }
};

int main() {
    auto strategy = PaymentStrategyFactory::getStrategy("CreditCard");
    PaymentProcessor processor(move(strategy));
    processor.pay(100);

    processor.setStrategy(PaymentStrategyFactory::getStrategy("UPI"));
    processor.pay(50);

    return 0;
}
Line Notes
class PaymentStrategyFactory {Factory encapsulates strategy creation logic
static unique_ptr<PaymentStrategy> getStrategy(const string& method) {Returns appropriate strategy instance or throws error
void setStrategy(unique_ptr<PaymentStrategy> strat) {Allows changing strategy at runtime
strategy->pay(amount);Delegates payment processing to current strategy
}Closes the PaymentProcessor class
class PaymentStrategy {
    pay(amount) {
        throw new Error('pay() must be implemented');
    }
}

class CreditCardStrategy extends PaymentStrategy {
    pay(amount) {
        console.log(`Processing credit card payment of $${amount}`);
    }
}

class UPIStrategy extends PaymentStrategy {
    pay(amount) {
        console.log(`Processing UPI payment of $${amount}`);
    }
}

class PaymentStrategyFactory {
    static getStrategy(method) {
        switch (method) {
            case 'CreditCard': return new CreditCardStrategy();
            case 'UPI': return new UPIStrategy();
            default: throw new Error('Invalid payment method');
        }
    }
}

class PaymentProcessor {
    constructor(strategy) {
        this.strategy = strategy;
    }
    setStrategy(strategy) {
        this.strategy = strategy;
    }
    pay(amount) {
        this.strategy.pay(amount);
    }
}

// Driver code
const processor = new PaymentProcessor(PaymentStrategyFactory.getStrategy('CreditCard'));
processor.pay(100);
processor.setStrategy(PaymentStrategyFactory.getStrategy('UPI'));
processor.pay(50);
Line Notes
class PaymentStrategyFactory {Factory centralizes strategy creation
static getStrategy(method) {Returns appropriate strategy instance or throws error
setStrategy(strategy) {Allows runtime switching of strategy
this.strategy.pay(amount);Delegates payment processing to current strategy
}Closes the PaymentProcessor class
Complexity
TimeO(1)
SpaceO(1)

Strategy selection and payment processing are constant time operations; factory adds minimal overhead.

💡 This approach is optimal for maintainability and runtime flexibility, with negligible performance cost.
Interview Verdict: Accepted and best practice

This is the recommended approach in professional codebases and interviews for flexible, maintainable design.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, coding the Strategy pattern with runtime selection (Approach 3) is ideal. Approach 1 is only for explanation, and Approach 2 is a good intermediate step.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute Force (Conditionals)O(1)O(1)NoN/AMention only - never code
2. Strategy Pattern with PolymorphismO(1)O(1)NoN/AGood to code if asked for design
3. Strategy Pattern with Factory and Runtime SelectionO(1)O(1)NoN/ABest approach to code and explain
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before your interview. Start by explaining the naive approach, then progressively improve your design. Practice coding each approach and explaining tradeoffs clearly.

How to Present

Step 1: Clarify the problem and confirm requirements (e.g., multiple payment methods, runtime selection)Step 2: Present the brute force approach using conditionals and discuss its drawbacksStep 3: Introduce the Strategy pattern with interfaces and polymorphismStep 4: Show how to select and switch strategies at runtime using factories or dependency injectionStep 5: Write clean, modular code and test with example inputs

Time Allocation

Clarify: 3min → Approach: 5min → Code: 10min → Test: 5min. Total ~23min

What the Interviewer Tests

The interviewer checks your understanding of polymorphism, design patterns, code extensibility, and ability to refactor messy conditional logic into clean OOP design.

Common Follow-ups

  • How would you add a new payment method without modifying existing code? → Add a new strategy class and update factory
  • How to handle errors or unsupported payment methods? → Throw exceptions or use default strategies
  • Can strategies share common code? → Use abstract base classes or composition
  • How to test strategies independently? → Unit test each concrete strategy class
💡 These follow-ups test your understanding of extensibility, error handling, code reuse, and testing in design patterns.
🔍
Pattern Recognition

When to Use

1. Multiple algorithms or behaviors exist for a task 2. Behavior needs to be selected or changed at runtime 3. Avoid complex conditional statements 4. Want to add new behaviors without modifying existing code

Signature Phrases

'Replace conditional logic with polymorphism''Select algorithm at runtime''Encapsulate behavior in separate classes'

NOT This Pattern When

Factory Pattern - creates objects but does not encapsulate interchangeable behavior; Decorator Pattern - adds responsibilities dynamically rather than replacing behavior

Similar Problems

State Pattern - manages object state transitions similarly but focuses on state changesTemplate Method Pattern - defines algorithm skeleton with fixed steps and variable partsCommand Pattern - encapsulates requests as objects for flexible command execution

Practice

(1/5)
1. When a class implements an interface and also extends an abstract class, what is the sequence of abstraction enforcement and implementation that occurs during object instantiation?
easy
A. The abstract class constructor runs, but interface methods have no implementation to run.
B. Interface methods are implemented first, then the abstract class constructor runs.
C. The class must implement interface methods before the abstract class constructor runs.
D. The abstract class's constructor runs first, then interface methods are implemented by the class.

Solution

  1. Step 1: Understand interface role

    Interfaces declare methods but provide no implementation or constructors.
  2. Step 2: Abstract class constructor behavior

    Abstract class constructors run during instantiation to initialize shared state.
  3. Step 3: Implementation of interface methods

    The class implementing the interface provides method bodies; no constructor or code runs from interface itself.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Interfaces define contracts only; abstract class constructors run during instantiation.
Hint: Interface methods have no constructor or implementation to run; abstract class constructors always run.
Common Mistakes:
  • Thinking interface methods have constructors or code to execute.
  • Believing interface implementation order affects constructor execution.
  • Confusing interface method implementation with constructor invocation.
2. In a large software system, when would applying the Interface Segregation Principle (ISP) be most beneficial?
easy
A. When clients depend on interfaces that contain methods they do not use, causing unnecessary implementation burden.
B. When all clients require the exact same set of methods, so a single fat interface simplifies design.
C. When you want to enforce a strict inheritance hierarchy with minimal interfaces.
D. When you want to reduce the number of interfaces to simplify the codebase.

Solution

  1. Step 1: Understand ISP's goal

    ISP aims to prevent clients from depending on methods they don't use, avoiding fat interfaces that force unnecessary implementations.
  2. Step 2: Analyze options

    When clients depend on interfaces that contain methods they do not use, causing unnecessary implementation burden. correctly identifies the scenario where ISP helps by splitting fat interfaces. When all clients require the exact same set of methods, so a single fat interface simplifies design. describes a scenario where ISP is less needed. When you want to enforce a strict inheritance hierarchy with minimal interfaces. confuses inheritance hierarchy with interface segregation. When you want to reduce the number of interfaces to simplify the codebase. incorrectly assumes fewer interfaces always simplify design, ignoring interface misuse.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    ISP is about splitting interfaces to avoid forcing clients to implement unused methods.
Hint: ISP splits interfaces so clients only depend on what they use.
Common Mistakes:
  • Believing fewer interfaces always mean better design
  • Thinking ISP applies when all clients use all methods
  • Confusing ISP with inheritance hierarchy rules
3. Which of the following is a common trade-off when using a Facade pattern in a large system?
medium
A. Facade can hide too much complexity, making it hard to access advanced features of subsystems
B. Facade increases coupling between client and subsystems by exposing detailed interfaces
C. Facade always adds significant runtime overhead due to extra method calls
D. Facade requires changing the underlying subsystem interfaces to work properly

Solution

  1. Step 1: Recall Facade's purpose

    Facade simplifies complex subsystems by providing a unified interface.
  2. Step 2: Analyze trade-offs

    While Facade simplifies usage, it can hide advanced features, limiting flexibility.
  3. Step 3: Evaluate other options

    A is incorrect because Facade reduces coupling by hiding subsystem details. C is incorrect; Facade's overhead is minimal. D is wrong; Facade does not require changing subsystems.
  4. Final Answer:

    Option A -> Option A
Hint: Facade hides complexity but may hide power
Common Mistakes:
  • Believing Facade increases coupling instead of reducing it
  • Assuming Facade adds heavy runtime overhead
  • Thinking Facade requires modifying subsystems
4. Which of the following statements about the Adapter pattern is INCORRECT?
medium
A. Adapter changes the interface of an existing object to match what the client expects
B. Adapter can be implemented using inheritance or composition
C. Adapter adds new functionality to the adapted object without modifying it
D. Adapter is used to simplify a complex subsystem by providing a unified interface

Solution

  1. Step 1: Review Adapter intent

    Adapter converts incompatible interfaces to make them compatible.
  2. Step 2: Check each statement

    A is correct: Adapter changes interface. B is correct: Adapter can use inheritance or composition. C is correct: Adapter can add behavior without modifying original object. D is incorrect: Simplifying a complex subsystem is Facade's role, not Adapter's.
  3. Final Answer:

    Option D -> Option D
Hint: Adapter = interface converter; Facade = interface simplifier
Common Mistakes:
  • Confusing Adapter with Facade's simplification role
  • Thinking Adapter only uses inheritance
  • Assuming Adapter cannot add new behavior
5. Considering extensibility in the Snake and Ladder game design, what is a key trade-off when embedding snakes and ladders directly as attributes inside the Board class versus modeling them as separate entities?
medium
A. Modeling snakes and ladders as separate entities increases runtime complexity significantly
B. Embedding snakes and ladders inside Board simplifies design but reduces flexibility to add new types of board elements later
C. Embedding snakes and ladders inside Board improves encapsulation and makes the Board immutable
D. Modeling snakes and ladders separately forces duplication of position data, increasing memory usage unnecessarily

Solution

  1. Step 1: Embedding snakes/ladders inside Board

    This approach simplifies initial design but tightly couples Board to these elements.
  2. Step 2: Impact on extensibility

    Tightly coupled design makes it harder to add new board elements (e.g., portals, traps) without modifying Board.
  3. Step 3: Modeling as separate entities

    Allows easy extension by adding new element types without changing Board internals.
  4. Step 4: Complexity and memory considerations

    Separate entities add minimal overhead; runtime complexity impact is negligible.
  5. Final Answer:

    Option B -> Option B
  6. Quick Check:

    Trade-off is between simplicity and extensibility, not runtime complexity or memory bloat.
Hint: Tight coupling simplifies now but blocks future extensions [OK]
Common Mistakes:
  • Thinking separate entities cause big runtime overhead
  • Believing embedding improves encapsulation and immutability
  • Assuming separate entities cause memory bloat