Bird
Raised Fist0
Interview Prepoop-design-patternsmediumAmazonGoogleMicrosoftFlipkartSwiggyRazorpayZepto

Factory vs Abstract Factory vs Builder - When to Use Each

Choose your preparation mode3 modes available
🎯
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. You are designing a system where a class needs to inherit features from two unrelated classes. Which type of inheritance best fits this scenario?
easy
A. Single inheritance
B. Multiple inheritance
C. Multilevel inheritance
D. Hierarchical inheritance

Solution

  1. Step 1: Understand inheritance types

    Single inheritance involves one parent and one child class, so it cannot inherit from two unrelated classes.
  2. Step 2: Multiple inheritance definition

    Multiple inheritance allows a class to inherit from more than one parent class, fitting the scenario.
  3. Step 3: Multilevel and hierarchical inheritance

    Multilevel is a chain of inheritance, hierarchical is one parent with multiple children, neither fits inheriting from two unrelated classes.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    Multiple inheritance is the only type that supports inheriting from multiple unrelated classes.
Hint: Multiple inheritance = multiple parents
Common Mistakes:
  • Confusing multilevel with multiple inheritance
  • Assuming hierarchical inheritance allows multiple parents
  • Thinking single inheritance can have multiple parents
2. When a new feature is added by extending a class hierarchy following the Open/Closed Principle, what is the typical sequence of events when the system executes a method call on the new subclass instance?
easy
A. The base class method is always executed first, then the subclass method overrides it afterward
B. The system duplicates the base class code inside the subclass to avoid modifying the base
C. The subclass method is invoked directly due to polymorphism, without modifying base class code
D. Both base and subclass methods execute sequentially because the base class is modified to call the subclass

Solution

  1. Step 1: Understand polymorphic dispatch

    Method calls on subclass instances invoke the subclass's overridden method directly.
  2. Step 2: Base class code remains unchanged

    The base class is closed for modification; no changes are made to call subclass methods explicitly.
  3. Step 3: Why other options are incorrect

    The base class method is always executed first, then the subclass method overrides it afterward incorrectly suggests base method runs first then subclass; Both base and subclass methods execute sequentially because the base class is modified to call the subclass implies base class modification; The system duplicates the base class code inside the subclass to avoid modifying the base suggests code duplication, violating DRY and OCP.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Polymorphism enables extension without modifying base code.
Hint: Polymorphism calls subclass methods directly [OK]
Common Mistakes:
  • Assuming base method always runs first
  • Believing base class must be modified to support extension
  • Thinking code duplication is a valid OCP strategy
3. Which of the following statements about using inheritance to model different vehicle types in the parking lot system is INCORRECT?
medium
A. Inheritance allows sharing common vehicle attributes and behaviors in a base Vehicle class
B. Using inheritance for vehicle types can lead to rigid designs that are hard to extend with new types
C. Inheritance is always the best approach to add new vehicle types without modifying existing code
D. Polymorphism enables treating all vehicle types uniformly when allocating parking spots

Solution

  1. Step 1: Review inheritance benefits

    Inheritance supports code reuse and polymorphism for uniform handling.
  2. Step 2: Recognize limitations

    Inheritance hierarchies can become rigid and hard to extend, violating open-closed principle.
  3. Step 3: Identify incorrect statement

    Claiming inheritance is always best ignores alternatives like composition or interfaces that improve extensibility.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Inheritance is not always best for extensibility [OK]
Hint: Inheritance can cause rigidity; prefer composition for extensibility [OK]
Common Mistakes:
  • Assuming inheritance is always the best design choice
  • Ignoring polymorphism benefits
4. Which of the following statements about the Liskov Substitution Principle is INCORRECT?
medium
A. A subclass can strengthen preconditions of an inherited method to ensure better input validation.
B. A subclass must not weaken postconditions of an inherited method.
C. Covariance in return types is allowed under LSP.
D. Contravariance in method parameter types is allowed under LSP.

Solution

  1. Step 1: Recall LSP precondition rule

    Subclasses must not strengthen preconditions; they can only maintain or weaken them.
  2. Step 2: Analyze each statement

    A subclass can strengthen preconditions of an inherited method to ensure better input validation. is incorrect because strengthening preconditions breaks substitutability. A subclass must not weaken postconditions of an inherited method. is correct; subclasses can weaken postconditions. Covariance in return types is allowed under LSP. is correct; covariance in return types is allowed. Contravariance in method parameter types is allowed under LSP. is correct; contravariance in parameter types is allowed.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Strengthening preconditions violates LSP.
Hint: Preconditions can only be weakened, not strengthened, in subclasses.
Common Mistakes:
  • Confusing precondition and postcondition rules
  • Believing strengthening preconditions is safe
  • Misunderstanding covariance and contravariance
5. What is a key limitation or trade-off of using method overloading extensively in a large codebase?
medium
A. It can cause ambiguity errors at compile time if parameter lists are too similar or implicit conversions apply
B. It increases runtime overhead due to dynamic dispatch and vtable lookups
C. It prevents subclasses from overriding methods with the same name
D. It requires all overloaded methods to have the same return type

Solution

  1. Step 1: Understand overloading resolution

    Overloading is resolved at compile time by matching method signatures.
  2. Step 2: Identify ambiguity risk

    If parameter lists are too similar or implicit conversions exist, the compiler may not decide which method to call, causing ambiguity errors.
  3. Step 3: Analyze options

    It can cause ambiguity errors at compile time if parameter lists are too similar or implicit conversions apply correctly identifies this compile-time ambiguity risk. It increases runtime overhead due to dynamic dispatch and vtable lookups incorrectly attributes runtime overhead to overloading (it's compile-time). It prevents subclasses from overriding methods with the same name is false; overloading does not prevent overriding. It requires all overloaded methods to have the same return type is false; return types can differ in overloading.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Overloading can cause compile-time ambiguity, not runtime overhead or overriding restrictions.
Hint: Overloading ambiguity arises from similar parameter lists, not runtime costs
Common Mistakes:
  • Confusing overloading with overriding runtime costs
  • Believing overloading restricts subclass method overriding
  • Assuming return types must be identical in overloading
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