Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartSwiggyRazorpayZepto

Factory vs Abstract Factory vs Builder - When to Use Each

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
🎯
Factory vs Abstract Factory vs Builder - When to Use Each
mediumOOPAmazonGoogleMicrosoft

Imagine you are designing a software system for a car manufacturing company that produces different types of cars and their parts. You need a flexible way to create families of related objects without specifying their concrete classes.

💡 This problem is about understanding when and why to use three common creational design patterns in object-oriented programming: Factory, Abstract Factory, and Builder. Beginners often struggle because these patterns seem similar but serve different purposes depending on the complexity and variability of object creation.
📋
Problem Statement

Given a scenario where you need to create objects in a software system, decide which creational design pattern to use among Factory, Abstract Factory, and Builder. Explain the differences, use cases, and provide example implementations for each pattern.

Focus on object creation flexibility and maintainabilityAvoid tight coupling between client code and concrete classesSupport families of related products or complex object construction
💡
Example
Input"Need to create different types of vehicles (Car, Bike) with different parts (Engine, Tires)"
OutputUse Abstract Factory to create families of related vehicle parts

Abstract Factory allows creating related objects without specifying their concrete classes, ensuring compatibility among parts.

Input"Need to create a complex object like a House with many optional parts"
OutputUse Builder to construct the House step-by-step

Builder separates the construction of a complex object from its representation, allowing stepwise creation.

  • Creating only one type of product with no variants → Factory is sufficient
  • Creating multiple families of related products → Abstract Factory is preferred
  • Constructing complex objects with many optional parameters → Builder is ideal
  • Changing product representation without changing construction process → Builder helps
⚠️
Common Mistakes
Confusing Factory with Abstract Factory

Trying to create multiple related products with Factory leads to inconsistent product families

Use Abstract Factory to group related product creation methods

Using Builder for simple object creation

Unnecessary complexity and verbose code for straightforward object instantiation

Use Factory or direct instantiation for simple objects

Not separating product interfaces from concrete classes

Client code becomes tightly coupled to concrete implementations, reducing flexibility

Always define abstract product interfaces and program to them

Ignoring error handling in factory methods

Runtime errors or crashes when unknown product types are requested

Add proper error handling or exceptions for invalid inputs

Mixing responsibilities of Director and Builder

Confused code where Director does construction and product assembly, reducing clarity

Keep Director focused on construction sequence, Builder on product assembly

🧠
Factory Pattern - Simple Object Creation
💡 Start with Factory Pattern because it introduces the concept of encapsulating object creation, which is the foundation for more complex patterns.

Intuition

The Factory Pattern provides a method to create objects without exposing the instantiation logic to the client, promoting loose coupling.

Algorithm

  1. Define a common interface for products.
  2. Create concrete product classes implementing the interface.
  3. Implement a factory class with a method that returns product instances based on input.
  4. Client calls the factory method to get product objects without knowing the concrete classes.
💡 These steps show how to separate object creation from usage, which helps in managing dependencies and future changes.
</>
Code
from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def drive(self):
        pass

class Car(Vehicle):
    def drive(self):
        return "Driving a car"

class Bike(Vehicle):
    def drive(self):
        return "Riding a bike"

class VehicleFactory:
    @staticmethod
    def create_vehicle(vehicle_type: str) -> Vehicle:
        if vehicle_type == "car":
            return Car()
        elif vehicle_type == "bike":
            return Bike()
        else:
            raise ValueError("Unknown vehicle type")

# Driver code
if __name__ == "__main__":
    factory = VehicleFactory()
    vehicle = factory.create_vehicle("car")
    print(vehicle.drive())
Line Notes
class Vehicle(ABC):Defines an abstract base class to enforce a common interface for all vehicles
def drive(self):Abstract method that all concrete vehicles must implement
class VehicleFactory:Factory class responsible for creating vehicle instances
def create_vehicle(vehicle_type: str) -> Vehicle:Static method to encapsulate object creation logic based on input
interface Vehicle {
    String drive();
}

class Car implements Vehicle {
    public String drive() {
        return "Driving a car";
    }
}

class Bike implements Vehicle {
    public String drive() {
        return "Riding a bike";
    }
}

class VehicleFactory {
    public static Vehicle createVehicle(String type) {
        if (type.equalsIgnoreCase("car")) {
            return new Car();
        } else if (type.equalsIgnoreCase("bike")) {
            return new Bike();
        } else {
            throw new IllegalArgumentException("Unknown vehicle type");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle vehicle = VehicleFactory.createVehicle("car");
        System.out.println(vehicle.drive());
    }
}
Line Notes
interface Vehicle {Defines the contract for all vehicle types
public static Vehicle createVehicle(String type) {Factory method to create vehicles based on type
if (type.equalsIgnoreCase("car")) {Checks input to decide which concrete vehicle to instantiate
throw new IllegalArgumentException("Unknown vehicle type");Handles invalid input gracefully
#include <iostream>
#include <memory>
#include <string>

class Vehicle {
public:
    virtual std::string drive() = 0;
    virtual ~Vehicle() {}
};

class Car : public Vehicle {
public:
    std::string drive() override {
        return "Driving a car";
    }
};

class Bike : public Vehicle {
public:
    std::string drive() override {
        return "Riding a bike";
    }
};

class VehicleFactory {
public:
    static std::unique_ptr<Vehicle> createVehicle(const std::string& type) {
        if (type == "car") {
            return std::make_unique<Car>();
        } else if (type == "bike") {
            return std::make_unique<Bike>();
        } else {
            throw std::invalid_argument("Unknown vehicle type");
        }
    }
};

int main() {
    auto vehicle = VehicleFactory::createVehicle("car");
    std::cout << vehicle->drive() << std::endl;
    return 0;
}
Line Notes
class Vehicle {Abstract base class defining the vehicle interface
virtual std::string drive() = 0;Pure virtual function to enforce implementation in derived classes
static std::unique_ptr<Vehicle> createVehicle(const std::string& type) {Static factory method returning smart pointer to manage memory
throw std::invalid_argument("Unknown vehicle type");Exception handling for invalid input
class Vehicle {
    drive() {
        throw new Error("Method 'drive()' must be implemented.");
    }
}

class Car extends Vehicle {
    drive() {
        return "Driving a car";
    }
}

class Bike extends Vehicle {
    drive() {
        return "Riding a bike";
    }
}

class VehicleFactory {
    static createVehicle(type) {
        if (type === "car") {
            return new Car();
        } else if (type === "bike") {
            return new Bike();
        } else {
            throw new Error("Unknown vehicle type");
        }
    }
}

// Driver code
try {
    const vehicle = VehicleFactory.createVehicle("car");
    console.log(vehicle.drive());
} catch (e) {
    console.error(e.message);
}
Line Notes
class Vehicle {Base class defining the interface for vehicles
drive() { throw new Error(...) }Enforces subclasses to implement the drive method
static createVehicle(type) {Factory method to create vehicle instances based on type
throw new Error("Unknown vehicle type");Error handling for unsupported vehicle types
Complexity
TimeO(1)
SpaceO(1)

Object creation is direct and constant time; no complex data structures used.

💡 For any input, the factory instantly returns an object, so even for large inputs, performance is stable.
Interview Verdict: Accepted

This approach is simple and efficient for single product creation scenarios, making it a good starting point.

🧠
Abstract Factory Pattern - Families of Related Objects
💡 Abstract Factory extends Factory by allowing creation of families of related objects without specifying their concrete classes, which is useful when products must be used together.

Intuition

It provides an interface for creating related objects, ensuring that the client uses compatible products from the same family.

Algorithm

  1. Define interfaces for each kind of product (e.g., Chair, Sofa).
  2. Create concrete product classes for each family (e.g., VictorianChair, ModernChair).
  3. Define an abstract factory interface with methods to create each product type.
  4. Implement concrete factories for each family that create corresponding products.
  5. Client uses the abstract factory interface to create product families without knowing concrete classes.
💡 This approach enforces consistency among products and decouples client code from concrete implementations.
</>
Code
from abc import ABC, abstractmethod

# Product interfaces
class Chair(ABC):
    @abstractmethod
    def sit_on(self):
        pass

class Sofa(ABC):
    @abstractmethod
    def lie_on(self):
        pass

# Concrete products - Victorian
class VictorianChair(Chair):
    def sit_on(self):
        return "Sitting on Victorian Chair"

class VictorianSofa(Sofa):
    def lie_on(self):
        return "Lying on Victorian Sofa"

# Concrete products - Modern
class ModernChair(Chair):
    def sit_on(self):
        return "Sitting on Modern Chair"

class ModernSofa(Sofa):
    def lie_on(self):
        return "Lying on Modern Sofa"

# Abstract Factory
class FurnitureFactory(ABC):
    @abstractmethod
    def create_chair(self) -> Chair:
        pass

    @abstractmethod
    def create_sofa(self) -> Sofa:
        pass

# Concrete Factories
class VictorianFurnitureFactory(FurnitureFactory):
    def create_chair(self) -> Chair:
        return VictorianChair()

    def create_sofa(self) -> Sofa:
        return VictorianSofa()

class ModernFurnitureFactory(FurnitureFactory):
    def create_chair(self) -> Chair:
        return ModernChair()

    def create_sofa(self) -> Sofa:
        return ModernSofa()

# Client code
if __name__ == "__main__":
    factory = VictorianFurnitureFactory()
    chair = factory.create_chair()
    sofa = factory.create_sofa()
    print(chair.sit_on())
    print(sofa.lie_on())
Line Notes
class Chair(ABC):Defines the interface for Chair products
class FurnitureFactory(ABC):Abstract factory interface declaring creation methods for each product
class VictorianFurnitureFactory(FurnitureFactory):Concrete factory creating Victorian style products
chair = factory.create_chair()Client uses factory to get product without knowing concrete class
interface Chair {
    String sitOn();
}

interface Sofa {
    String lieOn();
}

class VictorianChair implements Chair {
    public String sitOn() {
        return "Sitting on Victorian Chair";
    }
}

class VictorianSofa implements Sofa {
    public String lieOn() {
        return "Lying on Victorian Sofa";
    }
}

class ModernChair implements Chair {
    public String sitOn() {
        return "Sitting on Modern Chair";
    }
}

class ModernSofa implements Sofa {
    public String lieOn() {
        return "Lying on Modern Sofa";
    }
}

interface FurnitureFactory {
    Chair createChair();
    Sofa createSofa();
}

class VictorianFurnitureFactory implements FurnitureFactory {
    public Chair createChair() {
        return new VictorianChair();
    }
    public Sofa createSofa() {
        return new VictorianSofa();
    }
}

class ModernFurnitureFactory implements FurnitureFactory {
    public Chair createChair() {
        return new ModernChair();
    }
    public Sofa createSofa() {
        return new ModernSofa();
    }
}

public class Main {
    public static void main(String[] args) {
        FurnitureFactory factory = new VictorianFurnitureFactory();
        Chair chair = factory.createChair();
        Sofa sofa = factory.createSofa();
        System.out.println(chair.sitOn());
        System.out.println(sofa.lieOn());
    }
}
Line Notes
interface Chair {Defines the Chair product interface
interface FurnitureFactory {Abstract factory interface declaring product creation methods
class VictorianFurnitureFactory implements FurnitureFactory {Concrete factory producing Victorian style products
FurnitureFactory factory = new VictorianFurnitureFactory();Client code uses abstract factory interface
#include <iostream>
#include <memory>
#include <string>

class Chair {
public:
    virtual std::string sitOn() = 0;
    virtual ~Chair() {}
};

class Sofa {
public:
    virtual std::string lieOn() = 0;
    virtual ~Sofa() {}
};

class VictorianChair : public Chair {
public:
    std::string sitOn() override {
        return "Sitting on Victorian Chair";
    }
};

class VictorianSofa : public Sofa {
public:
    std::string lieOn() override {
        return "Lying on Victorian Sofa";
    }
};

class ModernChair : public Chair {
public:
    std::string sitOn() override {
        return "Sitting on Modern Chair";
    }
};

class ModernSofa : public Sofa {
public:
    std::string lieOn() override {
        return "Lying on Modern Sofa";
    }
};

class FurnitureFactory {
public:
    virtual std::unique_ptr<Chair> createChair() = 0;
    virtual std::unique_ptr<Sofa> createSofa() = 0;
    virtual ~FurnitureFactory() {}
};

class VictorianFurnitureFactory : public FurnitureFactory {
public:
    std::unique_ptr<Chair> createChair() override {
        return std::make_unique<VictorianChair>();
    }
    std::unique_ptr<Sofa> createSofa() override {
        return std::make_unique<VictorianSofa>();
    }
};

class ModernFurnitureFactory : public FurnitureFactory {
public:
    std::unique_ptr<Chair> createChair() override {
        return std::make_unique<ModernChair>();
    }
    std::unique_ptr<Sofa> createSofa() override {
        return std::make_unique<ModernSofa>();
    }
};

int main() {
    std::unique_ptr<FurnitureFactory> factory = std::make_unique<VictorianFurnitureFactory>();
    auto chair = factory->createChair();
    auto sofa = factory->createSofa();
    std::cout << chair->sitOn() << std::endl;
    std::cout << sofa->lieOn() << std::endl;
    return 0;
}
Line Notes
class Chair {Abstract base class for Chair product
class FurnitureFactory {Abstract factory interface declaring creation methods
class VictorianFurnitureFactory : public FurnitureFactory {Concrete factory for Victorian style products
std::unique_ptr<Chair> createChair() override {Creates and returns a VictorianChair instance
class Chair {
    sitOn() {
        throw new Error("Method 'sitOn()' must be implemented.");
    }
}

class Sofa {
    lieOn() {
        throw new Error("Method 'lieOn()' must be implemented.");
    }
}

class VictorianChair extends Chair {
    sitOn() {
        return "Sitting on Victorian Chair";
    }
}

class VictorianSofa extends Sofa {
    lieOn() {
        return "Lying on Victorian Sofa";
    }
}

class ModernChair extends Chair {
    sitOn() {
        return "Sitting on Modern Chair";
    }
}

class ModernSofa extends Sofa {
    lieOn() {
        return "Lying on Modern Sofa";
    }
}

class FurnitureFactory {
    createChair() {
        throw new Error("Method 'createChair()' must be implemented.");
    }
    createSofa() {
        throw new Error("Method 'createSofa()' must be implemented.");
    }
}

class VictorianFurnitureFactory extends FurnitureFactory {
    createChair() {
        return new VictorianChair();
    }
    createSofa() {
        return new VictorianSofa();
    }
}

class ModernFurnitureFactory extends FurnitureFactory {
    createChair() {
        return new ModernChair();
    }
    createSofa() {
        return new ModernSofa();
    }
}

// Client code
try {
    const factory = new VictorianFurnitureFactory();
    const chair = factory.createChair();
    const sofa = factory.createSofa();
    console.log(chair.sitOn());
    console.log(sofa.lieOn());
} catch (e) {
    console.error(e.message);
}
Line Notes
class Chair {Base class defining Chair interface
class FurnitureFactory {Abstract factory declaring creation methods
class VictorianFurnitureFactory extends FurnitureFactory {Concrete factory for Victorian products
const chair = factory.createChair();Client obtains product without knowing concrete class
Complexity
TimeO(1)
SpaceO(1)

Each product creation is direct and constant time; no additional overhead.

💡 Even with multiple product types, creation remains efficient and predictable.
Interview Verdict: Accepted

Abstract Factory is the right choice when multiple related products must be created together, ensuring consistency.

🧠
Builder Pattern - Stepwise Complex Object Construction
💡 Builder Pattern is used when creating complex objects step-by-step, especially when the object has many optional parts or configurations.

Intuition

It separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

Algorithm

  1. Define a product class representing the complex object.
  2. Create a builder interface specifying methods to build parts of the product.
  3. Implement concrete builders that construct and assemble parts.
  4. Create a director class that controls the building process using a builder.
  5. Client uses the director to construct the product step-by-step.
💡 This approach clarifies complex construction logic and allows reuse of building steps for different product variants.
</>
Code
class House:
    def __init__(self):
        self.parts = []

    def add(self, part):
        self.parts.append(part)

    def __str__(self):
        return "House parts: " + ", ".join(self.parts)

class HouseBuilder:
    def __init__(self):
        self.house = House()

    def build_walls(self):
        self.house.add("Walls")

    def build_roof(self):
        self.house.add("Roof")

    def build_pool(self):
        self.house.add("Pool")

    def get_result(self):
        return self.house

class Director:
    def __init__(self, builder):
        self.builder = builder

    def construct_basic_house(self):
        self.builder.build_walls()
        self.builder.build_roof()

    def construct_luxury_house(self):
        self.builder.build_walls()
        self.builder.build_roof()
        self.builder.build_pool()

# Driver code
if __name__ == "__main__":
    builder = HouseBuilder()
    director = Director(builder)
    director.construct_luxury_house()
    house = builder.get_result()
    print(house)
Line Notes
class House:Represents the complex product being built
def add(self, part):Allows adding parts to the house incrementally
class HouseBuilder:Builder class encapsulating construction steps
def get_result(self):Returns the fully constructed product
import java.util.ArrayList;
import java.util.List;

class House {
    private List<String> parts = new ArrayList<>();

    public void add(String part) {
        parts.add(part);
    }

    public String toString() {
        return "House parts: " + String.join(", ", parts);
    }
}

class HouseBuilder {
    private House house = new House();

    public void buildWalls() {
        house.add("Walls");
    }

    public void buildRoof() {
        house.add("Roof");
    }

    public void buildPool() {
        house.add("Pool");
    }

    public House getResult() {
        return house;
    }
}

class Director {
    private HouseBuilder builder;

    public Director(HouseBuilder builder) {
        this.builder = builder;
    }

    public void constructBasicHouse() {
        builder.buildWalls();
        builder.buildRoof();
    }

    public void constructLuxuryHouse() {
        builder.buildWalls();
        builder.buildRoof();
        builder.buildPool();
    }
}

public class Main {
    public static void main(String[] args) {
        HouseBuilder builder = new HouseBuilder();
        Director director = new Director(builder);
        director.constructLuxuryHouse();
        House house = builder.getResult();
        System.out.println(house);
    }
}
Line Notes
class House {Product class representing the complex object
public void add(String part) {Method to add parts to the house
class HouseBuilder {Builder class defining construction steps
public House getResult() {Returns the constructed house
#include <iostream>
#include <vector>
#include <string>
#include <memory>

class House {
    std::vector<std::string> parts;
public:
    void add(const std::string& part) {
        parts.push_back(part);
    }
    void show() const {
        std::cout << "House parts: ";
        for (size_t i = 0; i < parts.size(); ++i) {
            std::cout << parts[i];
            if (i != parts.size() - 1) std::cout << ", ";
        }
        std::cout << std::endl;
    }
};

class HouseBuilder {
    std::unique_ptr<House> house;
public:
    HouseBuilder() : house(std::make_unique<House>()) {}
    void buildWalls() {
        house->add("Walls");
    }
    void buildRoof() {
        house->add("Roof");
    }
    void buildPool() {
        house->add("Pool");
    }
    std::unique_ptr<House> getResult() {
        return std::move(house);
    }
};

class Director {
    HouseBuilder& builder;
public:
    Director(HouseBuilder& b) : builder(b) {}
    void constructBasicHouse() {
        builder.buildWalls();
        builder.buildRoof();
    }
    void constructLuxuryHouse() {
        builder.buildWalls();
        builder.buildRoof();
        builder.buildPool();
    }
};

int main() {
    HouseBuilder builder;
    Director director(builder);
    director.constructLuxuryHouse();
    auto house = builder.getResult();
    house->show();
    return 0;
}
Line Notes
class House {Represents the complex product with multiple parts
void add(const std::string& part) {Adds parts to the house incrementally
class HouseBuilder {Builder class encapsulating construction logic
std::unique_ptr<House> getResult() {Transfers ownership of the constructed house to client
class House {
    constructor() {
        this.parts = [];
    }
    add(part) {
        this.parts.push(part);
    }
    toString() {
        return `House parts: ${this.parts.join(", ")}`;
    }
}

class HouseBuilder {
    constructor() {
        this.house = new House();
    }
    buildWalls() {
        this.house.add("Walls");
    }
    buildRoof() {
        this.house.add("Roof");
    }
    buildPool() {
        this.house.add("Pool");
    }
    getResult() {
        return this.house;
    }
}

class Director {
    constructor(builder) {
        this.builder = builder;
    }
    constructBasicHouse() {
        this.builder.buildWalls();
        this.builder.buildRoof();
    }
    constructLuxuryHouse() {
        this.builder.buildWalls();
        this.builder.buildRoof();
        this.builder.buildPool();
    }
}

// Driver code
const builder = new HouseBuilder();
const director = new Director(builder);
director.constructLuxuryHouse();
const house = builder.getResult();
console.log(house.toString());
Line Notes
class House {Product class representing the complex object
add(part) {Method to add parts to the house incrementally
class HouseBuilder {Builder class defining construction steps
getResult() {Returns the fully constructed house
Complexity
TimeO(k)
SpaceO(k)

Construction involves k steps (parts), each adding constant time and space.

💡 For a house with 5 parts, expect roughly 5 operations, which is efficient and manageable.
Interview Verdict: Accepted

Builder is ideal for complex object creation scenarios where stepwise construction is needed.

📊
All Approaches - One-Glance Tradeoffs
💡 In most interviews, start with Factory for simple cases, Abstract Factory for related products, and Builder for complex constructions. Builder is less common but important for complex scenarios.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Factory PatternO(1)O(1)NoN/AMention and code for simple object creation
2. Abstract Factory PatternO(1)O(1)NoN/AMention and code for families of related products
3. Builder PatternO(k) where k is number of partsO(k)NoYesMention and code for complex object construction
💼
Interview Strategy
💡 Use this guide to understand the differences and appropriate use cases for Factory, Abstract Factory, and Builder patterns. Read it before interviews to confidently explain and implement these patterns.

How to Present

Step 1: Clarify the problem requirements and object creation complexity.Step 2: Start with Factory Pattern for simple object creation.Step 3: Introduce Abstract Factory when multiple related products are involved.Step 4: Explain Builder for complex objects requiring stepwise construction.Step 5: Discuss tradeoffs and why each pattern fits specific scenarios.

Time Allocation

Clarify: 3min → Approach explanation: 5min → Code: 10min → Testing & discussion: 7min. Total ~25min

What the Interviewer Tests

Interviewers check your understanding of object creation patterns, ability to choose the right pattern based on problem context, and your skill in implementing them cleanly.

Common Follow-ups

  • How would you extend Abstract Factory to support new product families? → Add new concrete factories and product classes.
  • Can Builder be used without a Director? → Yes, client can call builder methods directly for more control.
💡 These follow-ups test your deeper understanding of pattern flexibility and extensibility.
🔍
Pattern Recognition

When to Use

1) Need to encapsulate object creation; 2) Multiple related products must be created together; 3) Complex objects with many optional parts; 4) Want to decouple client from concrete classes.

Signature Phrases

"Create objects without exposing instantiation""Families of related products""Construct complex objects step-by-step"

NOT This Pattern When

Singleton Pattern (controls instance count), Prototype Pattern (cloning objects), Factory Method (single product creation with inheritance)

Similar Problems

Factory Pattern Implementation - basic object creationAbstract Factory Pattern Example - creating related product familiesBuilder Pattern Usage - constructing complex objects

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. When an object composed of multiple behaviors receives a request to perform an action, what is the typical sequence of internal steps that occur to fulfill this request?
easy
A. The object delegates the action to one or more composed behavior objects which execute their respective parts
B. The object checks flags internally and runs conditional code for each behavior
C. The object directly executes the action code inherited from its superclass
D. The object creates a new subclass instance dynamically to handle the action

Solution

  1. Step 1: Understand delegation in composition

    In composition, the main object delegates responsibilities to composed behavior objects rather than handling all logic itself.
  2. Step 2: Analyze each option

    The object directly executes the action code inherited from its superclass describes inheritance, not composition. The object checks flags internally and runs conditional code for each behavior implies flag-based conditional logic, which is less flexible. The object creates a new subclass instance dynamically to handle the action is not a typical or practical approach.
  3. Step 3: Confirm correct flow

    The composed behaviors receive the delegated call and execute their specific logic, enabling modular and maintainable design.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Delegation to composed objects is the hallmark of composition-based design.
Hint: Composition means delegation to behavior objects, not direct inheritance execution.
Common Mistakes:
  • Confusing inheritance method calls with composition delegation
  • Assuming flags control behavior execution internally
3. You are designing a system that manages user accounts and sends notification emails. According to the Single Responsibility Principle, how should you organize these responsibilities?
easy
A. Separate user account management and email notification into different classes because each has a different reason to change.
B. Combine user account management and email notification in one class because they are related to users.
C. Put all user-related functionalities, including notifications, into a single class to reduce the number of classes.
D. Create one class for user management and embed email notification logic inside its methods to simplify interactions.

Solution

  1. Step 1: Identify reasons to change

    User account management changes when user data or authentication changes; email notifications change when messaging or delivery requirements change.
  2. Step 2: Apply SRP

    Since these reasons to change differ, they should be separated into different classes to avoid coupling unrelated changes.
  3. Step 3: Evaluate other options

    Options A, C, and D combine responsibilities, increasing coupling and reducing cohesion, violating SRP.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Separate classes for distinct reasons to change -> SRP compliant.
Hint: One class, one reason to change.
Common Mistakes:
  • Assuming related domain means same responsibility.
  • Combining functionalities to reduce class count.
  • Embedding multiple responsibilities for convenience.
4. Which of the following statements about the Diamond Problem and its resolution is INCORRECT?
medium
A. The Diamond Problem occurs when a class inherits from two classes that both inherit from the same base class.
B. The Diamond Problem can be resolved by using virtual inheritance in languages like C++.
C. Method Resolution Order (MRO) is irrelevant to resolving the Diamond Problem.
D. Without proper resolution, the Diamond Problem can cause duplicate base class instances.

Solution

  1. Step 1: Understand the Diamond Problem

    It arises when a class inherits from two classes that share a common ancestor, causing ambiguity and duplication.
  2. Step 2: Virtual inheritance role

    Virtual inheritance in C++ ensures only one instance of the base class exists, resolving duplication.
  3. Step 3: Role of MRO

    MRO is critical in languages like Python to determine method lookup order and resolve ambiguity.
  4. Step 4: Identify incorrect statement

    Method Resolution Order (MRO) is irrelevant to resolving the Diamond Problem. incorrectly claims MRO is irrelevant, which is false.
  5. Final Answer:

    Option C -> Option C
  6. Quick Check:

    MRO is essential for resolving method lookup in diamond inheritance scenarios.
Hint: MRO is key to diamond resolution in dynamic languages
Common Mistakes:
  • Ignoring MRO's role in method lookup
  • Confusing virtual inheritance with method resolution
  • Assuming diamond problem only causes ambiguity, not duplication
5. If a system uses Dependency Injection (DI) extensively but experiences runtime errors due to missing dependencies, what advanced approach can help detect these issues earlier and improve robustness?
hard
A. Manually instantiate all dependencies in client code to ensure correctness.
B. Rely solely on runtime exception handling to catch missing dependencies.
C. Avoid using interfaces and inject concrete classes directly to reduce complexity.
D. Use compile-time dependency injection frameworks or static analysis tools to verify dependency graphs.

Solution

  1. Step 1: Identify the problem

    Runtime errors from missing dependencies indicate lack of early validation.
  2. Step 2: Evaluate solutions

    Use compile-time dependency injection frameworks or static analysis tools to verify dependency graphs. suggests compile-time DI or static analysis, which can catch missing dependencies before runtime. Rely solely on runtime exception handling to catch missing dependencies. defers detection to runtime, which is less robust. Avoid using interfaces and inject concrete classes directly to reduce complexity. breaks DIP and reduces flexibility. Manually instantiate all dependencies in client code to ensure correctness. defeats DI benefits and increases coupling.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Compile-time checks improve reliability by catching DI issues early.
Hint: Use compile-time DI or static analysis to catch missing dependencies early.
Common Mistakes:
  • Relying only on runtime exceptions
  • Injecting concrete classes instead of abstractions
  • Manual instantiation defeating DI benefits