Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartSwiggyRazorpayZepto

Factory vs Abstract Factory vs Builder - When to Use Each

Choose your preparation mode3 modes available
▶
Steps
setup

Define Product Class: House

The House class is defined to represent the complex object being built. It contains a list of parts and methods to add parts and display the house composition.

💡 Defining the product class is essential because it represents the final object that the builder will construct step-by-step.
Line:class House: def __init__(self): self.parts = []
💡 The product class holds the components that will be assembled by the builder.
📊
Factory vs Abstract Factory vs Builder - When to Use Each - Watch the Algorithm Execute, Step by Step
Watching each step reveals how the Builder pattern separates construction from representation, making complex object creation manageable and flexible.
Step 1/11
·Active fill★Answer cell
Defines the product class that will be built stepwise.
House
−parts: List[str]
+__init__()
+add()
+__str__()
Builder interface holds and constructs the product.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+__init__()
+build_walls()
+build_roof()
+2 more
HouseBuilder ◆ House (1:1)
Builder method adds a specific part to the product.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+build_walls()
HouseBuilder ◆ House (1:1)
Builder method adds another specific part to the product.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+build_roof()
HouseBuilder ◆ House (1:1)
Builder method adds an optional part to the product.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+build_pool()
HouseBuilder ◆ House (1:1)
Builder exposes the final product after construction.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+get_result()
HouseBuilder ◆ House (1:1)
Director controls the building process using a builder.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+build_walls()
+build_roof()
+build_pool()
+1 more
Director
−builder: HouseBuilder
+__init__()
+construct_basic_house()
+construct_luxury_house()
HouseBuilder ◆ House (1:1)Director ◇ HouseBuilder (1:1)
Director sequences builder calls to create a basic product.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+build_walls()
+build_roof()
Director
−builder: HouseBuilder
+construct_basic_house()
HouseBuilder ◆ House (1:1)Director ◇ HouseBuilder (1:1)
Director sequences builder calls to create a luxury product.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+build_walls()
+build_roof()
+build_pool()
Director
−builder: HouseBuilder
+construct_luxury_house()
HouseBuilder ◆ House (1:1)Director ◇ HouseBuilder (1:1)
Client retrieves the constructed product from the builder.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
+get_result()
Director
−builder: HouseBuilder
HouseBuilder ◆ House (1:1)Director ◇ HouseBuilder (1:1)
Product displays its assembled parts confirming construction.
House
−parts: List[str]
+__init__()
+add()
+__str__()
HouseBuilder
−house: House
Director
−builder: HouseBuilder
HouseBuilder ◆ House (1:1)Director ◇ HouseBuilder (1:1)

Key Takeaways

✓ Builder pattern separates complex object construction from its representation.

This separation is hard to grasp from code alone but becomes clear when watching stepwise construction.

✓ Director controls the construction sequence, enabling different product variants.

Seeing the director call different builder methods clarifies how product complexity and customization are managed.

✓ Builder methods modularly add parts, allowing optional features to be included flexibly.

Watching each part addition step reveals how optional components like pools are integrated without changing the product class.

Practice

(1/5)
1. Trace the sequence of events when a player rolls the dice and lands on a ladder in a well-designed Snake and Ladder game. Which of the following best describes the correct order of internal state changes?
easy
A. Player rolls dice -> GameController calculates new position -> Board checks for ladder -> GameController updates Player position accordingly
B. Player rolls dice -> Player position updated -> Board checks for ladder -> Player position updated again if ladder found
C. Player rolls dice -> Board updates Player position directly if ladder found -> Player notified of new position
D. Player rolls dice -> Dice notifies Board -> Board updates Player position -> GameController confirms move

Solution

  1. Step 1: Dice roll triggers GameController

    Dice generates number; GameController uses it to calculate tentative position.
  2. Step 2: Board checks for ladder or snake

    GameController queries Board to see if new position has ladder or snake.
  3. Step 3: GameController updates Player position

    Based on Board's info, GameController updates Player's position accordingly.
  4. Step 4: Player position is finalized

    Player's position reflects ladder climb if applicable.
  5. Final Answer:

    Option A -> Option A
  6. Quick Check:

    Only GameController manages state changes; Board is queried but does not update Player directly.
Hint: GameController mediates all state changes after dice roll [OK]
Common Mistakes:
  • Assuming Player updates position before Board check
  • Believing Board directly updates Player position
  • Thinking Dice notifies Board
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. 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
4. Suppose you have a legacy system that violates the Open/Closed Principle by frequently modifying core classes for new features. What is the best strategy to refactor it to comply with OCP without breaking existing functionality?
hard
A. Replace all core classes with interfaces and rewrite all implementations from scratch
B. Use global variables to control behavior changes instead of modifying classes
C. Continue modifying core classes but add feature flags to isolate changes
D. Introduce abstract base classes or interfaces and create new subclasses for new features, leaving existing classes unchanged

Solution

  1. Step 1: Understand refactoring goals

    Refactor to enable extension via new subclasses without modifying existing tested classes.
  2. Step 2: Why introducing abstract base classes or interfaces and creating new subclasses for new features, leaving existing classes unchanged fits

    Introducing abstractions and subclassing allows adding features by extension, preserving existing code.
  3. Step 3: Why other options fail

    Replace all core classes with interfaces and rewrite all implementations from scratch is costly and risky; Continue modifying core classes but add feature flags to isolate changes continues violating OCP; Use global variables to control behavior changes instead of modifying classes introduces global state, increasing complexity and bugs.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Refactor by abstraction and extension, not rewrite or global state hacks.
Hint: Refactor with abstractions and subclasses, keep existing code intact [OK]
Common Mistakes:
  • Thinking rewrite is the only way to comply with OCP
  • Believing feature flags solve OCP violations
  • Using global variables as a workaround
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