Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartRazorpaySwiggyPhonePe

Strategy Pattern - Replace Conditionals with Polymorphism

Choose your preparation mode3 modes available
🎯
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. 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
2. Given the following Python code using deep copy, what will be printed after modifying the copy's scores list?
import copy

class Profile:
    def __init__(self, name, scores):
        self.name = name
        self.scores = scores

    def __deepcopy__(self, memo):
        new_name = copy.deepcopy(self.name, memo)
        new_scores = copy.deepcopy(self.scores, memo)
        return Profile(new_name, new_scores)

original = Profile('Alice', [10, 20])
copy_obj = copy.deepcopy(original)
print('Original scores:', original.scores)
print('Copy scores:', copy_obj.scores)
copy_obj.scores.append(30)
print('After modifying copy scores:')
print('Original scores:', original.scores)
print('Copy scores:', copy_obj.scores)
easy
A. Original scores: [10, 20] Copy scores: [10, 20] After modifying copy scores: Original scores: [10, 20, 30] Copy scores: [10, 20, 30]
B. Original scores: [10, 20] Copy scores: [10, 20] After modifying copy scores: Original scores: [10, 20] Copy scores: [10, 20, 30]
C. Original scores: [10, 20] Copy scores: [10, 20] After modifying copy scores: Original scores: [10, 20] Copy scores: [10, 20]
D. Original scores: [10, 20] Copy scores: [10, 20, 30] After modifying copy scores: Original scores: [10, 20] Copy scores: [10, 20, 30]

Solution

  1. Step 1: Trace initial print statements

    Both original.scores and copy_obj.scores start as [10, 20], so first two prints show identical lists.
  2. Step 2: Trace modification and final prints

    copy_obj.scores.append(30) modifies only the copy's scores list because deep copy created a new list. Original remains [10, 20]. Final prints reflect this separation.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Deep copy prevents shared nested list -> original unchanged [OK]
Hint: Deep copy isolates nested mutable objects [OK]
Common Mistakes:
  • Assuming append affects original due to shared reference
3. Consider the following Python singleton implementation using lazy initialization. What will be the output of the code below?
easy
A. false
B. Raises an exception
C. true
D. null

Solution

  1. Step 1: Trace instance creation

    First call to Singleton() triggers __new__, _instance is None, so a new instance is created and assigned to _instance.
  2. Step 2: Trace second instance creation

    Second call to Singleton() triggers __new__, _instance is not None, so the existing instance is returned.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Both variables point to the same instance, so 'is' returns true [OK]
Hint: 'is' checks identity; singleton returns same instance [OK]
Common Mistakes:
  • Assuming new instance is created each time
  • Confusing 'is' with '=='
  • Expecting null or error due to lazy init
4. 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
5. In a legacy system, a class handles both business logic and logging. You want to refactor it following SRP, but the logging code is tightly intertwined with business logic. What is the best approach to refactor this while respecting SRP?
hard
A. Ignore SRP in this case because legacy code should not be refactored.
B. Leave logging inside the class because extracting it would break existing functionality and increase risk.
C. Merge business logic and logging into a utility class to centralize all cross-cutting concerns.
D. Extract logging into a separate class and replace logging calls with calls to this new class, even if it requires modifying many places.

Solution

  1. Step 1: Identify responsibilities

    Business logic and logging are separate reasons to change.
  2. Step 2: Refactor strategy

    Extract logging into its own class to isolate changes related to logging.
  3. Step 3: Address tight coupling

    Though intertwined, refactoring calls to the new logging class improves maintainability and respects SRP.
  4. Step 4: Evaluate other options

    Options B and D avoid refactoring, risking future issues; C increases coupling by merging unrelated concerns.
  5. Final Answer:

    Option D -> Option D
  6. Quick Check:

    Extract and isolate responsibilities even if it requires effort -> SRP compliance.
Hint: Separate concerns even if intertwined; refactor stepwise.
Common Mistakes:
  • Avoiding refactoring due to perceived risk.
  • Merging unrelated concerns for convenience.
  • Ignoring SRP in legacy code.
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