Bird
Raised Fist0
Interview Prepoop-design-patternseasyAmazonGoogleMicrosoftFlipkartRazorpayCREDSwiggy

Single Responsibility Principle - One Class, One Reason to Change

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
Steps
setup

Initial Class with Multiple Responsibilities

We start with a single class 'Employee' that handles both employee data and report generation. This violates the Single Responsibility Principle because it has more than one reason to change.

💡 Identifying a class with multiple responsibilities is the first step to applying SRP.
Line:class Employee: def __init__(self, name, id): self.name = name self.id = id def generate_report(self): # generates report
💡 A class should have only one reason to change; here, 'Employee' mixes data and reporting.
📊
Single Responsibility Principle - One Class, One Reason to Change - Watch the Algorithm Execute, Step by Step
Watching the step-by-step refactoring helps you understand why SRP matters and how to identify and separate responsibilities in class design.
Step 1/10
·Active fillAnswer cell
Initial design violates SRP by combining data and reporting.
Employee
name: string
id: string
+__init__()
+generate_report()
Highlighting responsibilities within the class.
Employee
name: string
id: string
+__init__()
+generate_report()
Introduced ReportGenerator class to separate concerns.
Employee
name: string
id: string
+__init__()
ReportGenerator
+generate()
ReportGenerator Employee (1:1)
Removed report generation from Employee to enforce SRP.
Employee
name: string
id: string
+__init__()
ReportGenerator
+generate()
ReportGenerator Employee (1:1)
Added composition to delegate report generation.
Employee
name: string
id: string
report_generator: ReportGenerator
+__init__()
ReportGenerator
+generate()
Employee ReportGenerator (1:1)
Added delegation method to Employee.
Employee
name: string
id: string
report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ReportGenerator (1:1)
Confirmed SRP adherence after refactoring.
Employee
name: string
id: string
report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ReportGenerator (1:1)
Confirmed SRP adherence for ReportGenerator.
Employee
name: string
id: string
report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ReportGenerator (1:1)
Final design respects Single Responsibility Principle.
Employee
name: string
id: string
report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ReportGenerator (1:1)
SRP applied successfully; design is modular and maintainable.
Employee
name: string
id: string
report_generator: ReportGenerator
+__init__()
+generate_report()
ReportGenerator
+generate()
Employee ReportGenerator (1:1)

Key Takeaways

A class should have only one reason to change, which means it should have a single responsibility.

This insight is hard to see from code alone because multiple responsibilities can be hidden inside methods or fields.

Separating responsibilities into different classes improves maintainability and reduces coupling.

Visualizing the separation helps understand how SRP leads to modular design.

Delegation allows a class to expose functionality without violating SRP by forwarding tasks to dedicated classes.

Seeing delegation in the diagram clarifies how responsibilities remain separated while preserving interface usability.

Practice

(1/5)
1. In the object-oriented design of a Snake and Ladder game, which component is primarily responsible for managing the state transitions of a player's position after a dice roll?
easy
A. The Dice class, since it generates the number that determines movement
B. The Player class, as it holds the current position and updates it directly
C. The Board class, because it contains the snakes and ladders and applies their effects
D. The GameController class, which orchestrates the game flow and updates player positions accordingly

Solution

  1. Step 1: Understand the role of Dice

    The Dice only generates a random number; it does not manage state transitions.
  2. Step 2: Consider Player class responsibilities

    Player holds position but should not decide how to update it considering snakes or ladders.
  3. Step 3: Analyze Board class role

    Board knows snakes and ladders but does not manage player state transitions directly.
  4. Step 4: Role of GameController

    GameController coordinates dice roll, queries Board for snakes/ladders, and updates Player position accordingly.
  5. Final Answer:

    Option D -> Option D
  6. Quick Check:

    GameController centralizes state transitions, ensuring separation of concerns.
Hint: GameController orchestrates state changes, not Dice or Player alone [OK]
Common Mistakes:
  • Thinking Dice manages player position
  • Assuming Player updates position without Board's input
  • Believing Board directly changes player state
2. When a class inherits from multiple classes that have a method with the same name, describe the step-by-step process the Method Resolution Order (MRO) uses to determine which method is called.
easy
A. MRO uses a linearization algorithm that merges the order of parents and their ancestors to find the method.
B. MRO searches the first parent class fully before moving to the next parent class.
C. MRO always calls the method from the last parent class listed in the inheritance.
D. MRO randomly picks the method from any parent class that defines it.

Solution

  1. Step 1: Understand naive search

    MRO does not simply search the first parent class fully before moving to the next; it uses a more sophisticated approach.
  2. Step 2: Recognize MRO linearization

    MRO uses a specific linearization (like C3 linearization) that merges parent classes and their ancestors in a consistent order.
  3. Step 3: Eliminate incorrect options

    MRO always calls the method from the last parent class listed in the inheritance is incorrect because the last parent is not always chosen; order and ancestors matter. MRO randomly picks the method from any parent class that defines it is incorrect because MRO is deterministic, not random.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    MRO merges inheritance hierarchies to find the correct method in a predictable order.
Hint: MRO = deterministic linearization of inheritance graph
Common Mistakes:
  • Assuming simple left-to-right search suffices
  • Believing last parent always overrides
  • Thinking method choice is random
3. 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
4. Examine the following buggy CompositeIterator code snippet. Which line contains the subtle bug that causes incorrect traversal order?
medium
A. Line initializing self.stack without reversing children.
B. Line checking hasNext() before popping from stack.
C. Line popping component from stack.
D. Line returning the component after processing.

Solution

  1. Step 1: Identify stack initialization issue

    Stack is initialized with children in original order, not reversed, causing traversal order reversal.
  2. Step 2: Confirm impact on traversal order

    Without reversing, popping from stack yields children in reverse order, breaking expected traversal.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Reversing children on stack initialization fixes traversal order [OK]
Hint: Stack must reverse children to preserve traversal order [OK]
Common Mistakes:
  • Forgetting to reverse children on stack push
  • Misplacing hasNext() check
5. Suppose you want to implement a prototype pattern for an object that contains references to other objects which themselves may reference back to the original object (cyclic references). Which approach correctly handles deep copying in this scenario?
hard
A. Use a deep copy implementation with memoization to track already copied objects and avoid infinite recursion
B. Use a naive recursive deep copy without memoization, which will eventually copy all objects
C. Use shallow copy to avoid recursion issues, accepting shared nested references
D. Serialize the object to JSON and deserialize it, which naturally handles cyclic references

Solution

  1. Step 1: Understand cyclic references problem

    Naive recursion without tracking copied objects causes infinite recursion on cycles.
  2. Step 2: Evaluate solutions

    Memoization tracks already copied objects, preventing infinite loops and ensuring correct deep copy. Shallow copy shares references, breaking independence. JSON serialization cannot handle cycles and will fail.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Memoization prevents infinite recursion in cyclic graphs [OK]
Hint: Memoization is essential for deep copying cyclic object graphs [OK]
Common Mistakes:
  • Ignoring cycles causes infinite recursion or stack overflow