Skip to content

Factory Pattern

Create objects without knowing their exact type - let the factory decide!

Factory Pattern: Creating Objects the Smart Way

Section titled “Factory Pattern: Creating Objects the Smart Way”

Now let’s dive into the Factory Pattern - one of the most commonly used creational design patterns.

Imagine you’re ordering a pizza. You don’t need to know how the kitchen makes it - you just tell them “I want a pizza” and they handle the rest. The Factory Pattern works the same way!

The Factory Pattern lets you create objects without specifying their exact class. Instead of directly creating objects with new ClassName(), you ask a factory to create them for you.

The Factory Pattern is useful when:

  1. You don’t know the exact type of object you need until runtime
  2. Object creation is complex - Lots of setup or configuration needed
  3. You want to decouple object creation from object usage
  4. You need flexibility - Easy to add new types without changing existing code
  5. You want centralized control - All object creation happens in one place

What Happens If We Don’t Use Factory Pattern?

Section titled “What Happens If We Don’t Use Factory Pattern?”

Without the Factory Pattern, you might:

  • Scatter object creation throughout your code
  • Tightly couple your code to specific classes
  • Make it hard to add new types - Need to modify code everywhere
  • Duplicate creation logic - Same setup code in multiple places
  • Violate Open/Closed Principle - Need to modify code to extend functionality

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Factory Pattern works in practice - showing the sequence of interactions:

sequenceDiagram
    participant Client
    participant Factory as PizzaFactory
    participant Interface as Pizza Interface
    participant Concrete as ConcretePizza
    
    Client->>Factory: create_pizza("margherita")
    activate Factory
    Factory->>Concrete: new MargheritaPizza()
    activate Concrete
    Concrete-->>Factory: Pizza instance
    deactivate Concrete
    Factory-->>Client: Returns Pizza instance
    deactivate Factory
    Client->>Interface: pizza.prepare()
    activate Interface
    Interface-->>Client: "Preparing Margherita pizza..."
    deactivate Interface
    
    Note over Client,Concrete: Client doesn't know<br/>about concrete classes!

You’re building a pizza ordering system. Customers can order different types of pizzas (Margherita, Pepperoni, Veggie). Without a factory, you’d do this:

bad_pizza.py
# ❌ Without Factory Pattern - Direct creation everywhere
class MargheritaPizza:
def prepare(self):
return "Preparing Margherita pizza..."
class PepperoniPizza:
def prepare(self):
return "Preparing Pepperoni pizza..."
class VeggiePizza:
def prepare(self):
return "Preparing Veggie pizza..."
# Problem: Object creation scattered everywhere
def order_pizza(pizza_type: str):
if pizza_type == "margherita":
pizza = MargheritaPizza() # Direct creation
elif pizza_type == "pepperoni":
pizza = PepperoniPizza() # Direct creation
elif pizza_type == "veggie":
pizza = VeggiePizza() # Direct creation
else:
raise ValueError("Unknown pizza type")
return pizza.prepare()
# Every time you add a new pizza type, you need to modify this function!

Problems:

  • Need to modify order_pizza() every time you add a new pizza type
  • Creation logic is scattered
  • Hard to test - can’t easily swap implementations
  • Violates Open/Closed Principle
classDiagram
    class Pizza {
        <<abstract>>
        +prepare() str
    }
    class MargheritaPizza {
        +prepare() str
    }
    class PepperoniPizza {
        +prepare() str
    }
    class VeggiePizza {
        +prepare() str
    }
    class PizzaFactory {
        +create_pizza(type) Pizza
    }
    class Client {
        +order_pizza(type) str
    }
    
    Pizza <|-- MargheritaPizza : implements
    Pizza <|-- PepperoniPizza : implements
    Pizza <|-- VeggiePizza : implements
    PizzaFactory ..> Pizza : creates
    Client --> PizzaFactory : uses
    Client ..> Pizza : uses
pizza_factory.py
from abc import ABC, abstractmethod
# Step 1: Define the interface (what all pizzas can do)
class Pizza(ABC):
@abstractmethod
def prepare(self) -> str:
"""Prepare the pizza"""
pass
# Step 2: Create concrete pizza classes
class MargheritaPizza(Pizza):
def prepare(self) -> str:
return "Preparing Margherita pizza with tomato and mozzarella..."
class PepperoniPizza(Pizza):
def prepare(self) -> str:
return "Preparing Pepperoni pizza with pepperoni and cheese..."
class VeggiePizza(Pizza):
def prepare(self) -> str:
return "Preparing Veggie pizza with bell peppers and mushrooms..."
# Step 3: Create the Factory
class PizzaFactory:
"""Factory that creates pizzas based on type"""
@staticmethod
def create_pizza(pizza_type: str) -> Pizza:
"""Factory method - creates pizza based on type"""
pizzas = {
"margherita": MargheritaPizza,
"pepperoni": PepperoniPizza,
"veggie": VeggiePizza
}
pizza_class = pizzas.get(pizza_type.lower())
if not pizza_class:
raise ValueError(f"Unknown pizza type: {pizza_type}")
return pizza_class() # Create and return the pizza
# Step 4: Use the factory
def order_pizza(pizza_type: str) -> str:
"""Order pizza using factory - no need to know specific pizza classes!"""
pizza = PizzaFactory.create_pizza(pizza_type) # Factory handles creation
return pizza.prepare()
# Usage
print(order_pizza("margherita")) # Works!
print(order_pizza("pepperoni")) # Works!
print(order_pizza("veggie")) # Works!
# To add a new pizza type, just:
# 1. Create the class
# 2. Add it to the factory's dictionary
# No need to modify order_pizza()!

Real-World Software Example: Payment Processing System

Section titled “Real-World Software Example: Payment Processing System”

Now let’s see a realistic software example - a payment processing system that needs to handle different payment methods.

You’re building an e-commerce system that needs to process payments through different gateways (Stripe, PayPal, Square). Without Factory Pattern:

bad_payments.py
# ❌ Without Factory Pattern
class StripePayment:
def __init__(self, api_key: str):
self.api_key = api_key
# Complex initialization...
def process(self, amount: float) -> dict:
# Stripe-specific processing logic
return {"status": "success", "gateway": "stripe", "amount": amount}
class PayPalPayment:
def __init__(self, client_id: str, secret: str):
self.client_id = client_id
self.secret = secret
# Complex initialization...
def process(self, amount: float) -> dict:
# PayPal-specific processing logic
return {"status": "success", "gateway": "paypal", "amount": amount}
class SquarePayment:
def __init__(self, access_token: str):
self.access_token = access_token
# Complex initialization...
def process(self, amount: float) -> dict:
# Square-specific processing logic
return {"status": "success", "gateway": "square", "amount": amount}
# Problem: Complex creation logic scattered everywhere
def process_payment(gateway: str, amount: float, config: dict) -> dict:
"""Process payment - but creation logic is messy!"""
if gateway == "stripe":
payment = StripePayment(config["stripe_api_key"]) # Complex creation
elif gateway == "paypal":
payment = PayPalPayment(
config["paypal_client_id"],
config["paypal_secret"]
) # Different parameters!
elif gateway == "square":
payment = SquarePayment(config["square_access_token"]) # Yet another way!
else:
raise ValueError(f"Unknown gateway: {gateway}")
return payment.process(amount)
# Problems:
# - Different initialization for each gateway
# - Need to know all gateway details
# - Hard to add new gateways
# - Configuration management is messy

Problems:

  • Complex creation logic scattered throughout code
  • Need to know different initialization requirements for each gateway
  • Hard to add new payment gateways
  • Configuration management is messy
  • Tight coupling to specific payment classes
payment_factory.py
from abc import ABC, abstractmethod
from typing import Dict, Any
# Step 1: Define the payment interface
class PaymentProcessor(ABC):
"""Interface for all payment processors"""
@abstractmethod
def process(self, amount: float) -> Dict[str, Any]:
"""Process a payment"""
pass
# Step 2: Create concrete payment processors
class StripePayment(PaymentProcessor):
def __init__(self, api_key: str):
self.api_key = api_key
# Initialize Stripe SDK, validate API key, etc.
print(f"Initializing Stripe with API key: {api_key[:10]}...")
def process(self, amount: float) -> Dict[str, Any]:
# Stripe-specific processing
return {
"status": "success",
"gateway": "stripe",
"amount": amount,
"transaction_id": f"stripe_{amount}"
}
class PayPalPayment(PaymentProcessor):
def __init__(self, client_id: str, secret: str):
self.client_id = client_id
self.secret = secret
# Initialize PayPal SDK, authenticate, etc.
print(f"Initializing PayPal with client ID: {client_id[:10]}...")
def process(self, amount: float) -> Dict[str, Any]:
# PayPal-specific processing
return {
"status": "success",
"gateway": "paypal",
"amount": amount,
"transaction_id": f"paypal_{amount}"
}
class SquarePayment(PaymentProcessor):
def __init__(self, access_token: str):
self.access_token = access_token
# Initialize Square SDK, validate token, etc.
print(f"Initializing Square with access token: {access_token[:10]}...")
def process(self, amount: float) -> Dict[str, Any]:
# Square-specific processing
return {
"status": "success",
"gateway": "square",
"amount": amount,
"transaction_id": f"square_{amount}"
}
# Step 3: Create the Factory
class PaymentProcessorFactory:
"""Factory for creating payment processors"""
@staticmethod
def create_processor(gateway: str, config: Dict[str, str]) -> PaymentProcessor:
"""
Factory method - creates payment processor based on gateway type.
Handles all the complex initialization logic!
"""
gateway = gateway.lower()
if gateway == "stripe":
if "stripe_api_key" not in config:
raise ValueError("Missing stripe_api_key in config")
return StripePayment(config["stripe_api_key"])
elif gateway == "paypal":
if "paypal_client_id" not in config or "paypal_secret" not in config:
raise ValueError("Missing PayPal credentials in config")
return PayPalPayment(
config["paypal_client_id"],
config["paypal_secret"]
)
elif gateway == "square":
if "square_access_token" not in config:
raise ValueError("Missing square_access_token in config")
return SquarePayment(config["square_access_token"])
else:
raise ValueError(f"Unknown payment gateway: {gateway}")
# Step 4: Use the factory - clean and simple!
def process_payment(gateway: str, amount: float, config: Dict[str, str]) -> Dict[str, Any]:
"""
Process payment using factory.
No need to know about specific payment classes or their initialization!
"""
processor = PaymentProcessorFactory.create_processor(gateway, config)
return processor.process(amount)
# Usage - Clean and simple!
config = {
"stripe_api_key": "sk_test_1234567890",
"paypal_client_id": "paypal_client_123",
"paypal_secret": "paypal_secret_456",
"square_access_token": "sq_token_789"
}
# Process payments - factory handles all the complexity!
result1 = process_payment("stripe", 100.0, config)
print(result1)
result2 = process_payment("paypal", 50.0, config)
print(result2)
result3 = process_payment("square", 75.0, config)
print(result3)

With Factory Pattern, adding a new gateway is super easy:

adding_new_gateway.py
# Step 1: Create the new payment class
class ApplePayPayment(PaymentProcessor):
def __init__(self, merchant_id: str):
self.merchant_id = merchant_id
print(f"Initializing Apple Pay with merchant ID: {merchant_id}")
def process(self, amount: float) -> Dict[str, Any]:
return {
"status": "success",
"gateway": "apple_pay",
"amount": amount,
"transaction_id": f"apple_{amount}"
}
# Step 2: Add it to the factory (only change needed!)
class PaymentProcessorFactory:
@staticmethod
def create_processor(gateway: str, config: Dict[str, str]) -> PaymentProcessor:
gateway = gateway.lower()
# ... existing code ...
elif gateway == "apple_pay": # Just add this!
if "apple_pay_merchant_id" not in config:
raise ValueError("Missing apple_pay_merchant_id in config")
return ApplePayPayment(config["apple_pay_merchant_id"])
# ... rest of code ...
# That's it! No changes needed to process_payment() function!
# It automatically works with the new gateway!

There are several variations of the Factory Pattern:

The simplest form - a single method that creates objects:

simple_factory.py
class PizzaFactory:
@staticmethod
def create_pizza(pizza_type: str) -> Pizza:
"""Simple factory method"""
if pizza_type == "margherita":
return MargheritaPizza()
elif pizza_type == "pepperoni":
return PepperoniPizza()
else:
raise ValueError("Unknown pizza type")

Each product has its own factory:

factory_method.py
from abc import ABC, abstractmethod
class PizzaFactory(ABC):
"""Abstract factory"""
@abstractmethod
def create_pizza(self) -> Pizza:
pass
class MargheritaPizzaFactory(PizzaFactory):
def create_pizza(self) -> Pizza:
return MargheritaPizza()
class PepperoniPizzaFactory(PizzaFactory):
def create_pizza(self) -> Pizza:
return PepperoniPizza()
# Usage
factory = MargheritaPizzaFactory()
pizza = factory.create_pizza()

Creates families of related objects:

abstract_factory.py
from abc import ABC, abstractmethod
class UIFactory(ABC):
"""Abstract factory for UI components"""
@abstractmethod
def create_button(self):
pass
@abstractmethod
def create_dialog(self):
pass
class WindowsUIFactory(UIFactory):
def create_button(self):
return WindowsButton()
def create_dialog(self):
return WindowsDialog()
class MacUIFactory(UIFactory):
def create_button(self):
return MacButton()
def create_dialog(self):
return MacDialog()

Use Factory Pattern when:

Object creation is complex - Lots of setup, configuration, or validation
You don’t know the exact type until runtime
You want to decouple creation from usage
You need flexibility - Easy to add new types
You want centralized control - All creation in one place
You’re following Open/Closed Principle - Open for extension, closed for modification

Don’t use Factory Pattern when:

Simple object creation - If new Class() is enough, don’t overcomplicate
Only one type - If you only ever create one type, factory is unnecessary
Creation logic is trivial - Don’t add abstraction for simple cases
Performance is critical - Factory adds a small overhead (usually negligible)


overcomplicated.py
# ❌ Don't do this for simple cases!
class SimpleObjectFactory:
@staticmethod
def create():
return SimpleObject() # Just use SimpleObject() directly!
# ✅ Better: Direct instantiation for simple cases
obj = SimpleObject()
wrong_types.py
# ❌ Factory returning inconsistent types
class BadFactory:
@staticmethod
def create(type: str):
if type == "a":
return "string" # Returns string
elif type == "b":
return 42 # Returns int - inconsistent!
else:
return None # Returns None - even worse!
# ✅ Better: Factory should return consistent types
class GoodFactory:
@staticmethod
def create(type: str) -> BaseClass: # Always returns BaseClass
if type == "a":
return ClassA()
elif type == "b":
return ClassB()
else:
raise ValueError("Unknown type")

Mistake 3: Factory with Too Many Responsibilities

Section titled “Mistake 3: Factory with Too Many Responsibilities”
god_factory.py
# ❌ Factory doing too much
class GodFactory:
@staticmethod
def create(type: str):
# Creates object
obj = SomeClass()
# Validates it
obj.validate()
# Saves to database
obj.save()
# Sends notification
obj.notify()
# Logs everything
obj.log()
return obj # Factory should only create, not do everything!
# ✅ Better: Factory only creates, other classes handle other concerns
class GoodFactory:
@staticmethod
def create(type: str) -> BaseClass:
return SomeClass() # Just create and return!

  1. Decoupling - Client code doesn’t depend on concrete classes
  2. Flexibility - Easy to add new types without changing existing code
  3. Centralized Control - All creation logic in one place
  4. Hides Complexity - Client doesn’t need to know creation details
  5. Testability - Easy to mock factories for testing
  6. Follows SOLID Principles - Especially Open/Closed and Dependency Inversion

Factory Pattern is a creational design pattern that provides an interface for creating objects without specifying their exact classes. The factory decides which class to instantiate based on the input.

  • Decouple object creation from usage
  • Centralize creation logic
  • Simplify adding new types
  • Hide complex initialization
  • Follow Open/Closed Principle
  1. Define an interface - What all products can do
  2. Create concrete classes - Specific implementations
  3. Create a factory - Handles object creation
  4. Use the factory - Client asks factory to create objects
Client → Factory → Interface → Concrete Classes
  • Client - Uses the factory to get objects
  • Factory - Creates objects based on input
  • Interface - Defines what products can do
  • Concrete Classes - Specific implementations
# Interface
class Pizza(ABC):
@abstractmethod
def prepare(self): pass
# Concrete classes
class MargheritaPizza(Pizza): ...
class PepperoniPizza(Pizza): ...
# Factory
class PizzaFactory:
@staticmethod
def create(type: str) -> Pizza:
if type == "margherita":
return MargheritaPizza()
# ...
# Usage
pizza = PizzaFactory.create("margherita")

✅ Complex object creation
✅ Runtime type determination
✅ Need flexibility to add new types
✅ Want to decouple creation from usage
✅ Following Open/Closed Principle

❌ Simple object creation
❌ Only one type ever created
❌ Trivial creation logic
❌ Performance is critical

  • Factory Pattern = Create objects without knowing exact class
  • Factory = Centralized object creation
  • Interface = Common contract for all products
  • Benefit = Easy to extend, hard to break
  • Principle = Open for extension, closed for modification
# 1. Interface
class Product(ABC):
@abstractmethod
def do_something(self): pass
# 2. Concrete Products
class ProductA(Product): ...
class ProductB(Product): ...
# 3. Factory
class Factory:
@staticmethod
def create(type: str) -> Product:
# Creation logic here
pass
# 4. Usage
product = Factory.create("type_a")
product.do_something()
  • Factory Pattern simplifies object creation
  • It decouples client from concrete classes
  • It makes code more flexible and easier to extend
  • Use it when creation is complex or you need flexibility
  • Don’t use it for simple cases - avoid over-engineering!

What to say:

“Factory Pattern is a creational design pattern that provides an interface for creating objects without specifying their exact classes. The factory decides which class to instantiate based on the input.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of creational patterns category
  • Indicates you can explain concepts clearly

Must mention:

  • Complex object creation - When initialization involves multiple steps
  • Runtime type determination - When you don’t know the type until runtime
  • Decoupling - When you want to separate creation from usage
  • Extensibility - When you need to easily add new types
  • Open/Closed Principle - When you want to extend without modifying

Example scenario to give:

“I’d use Factory Pattern when building a payment processing system where I need to support multiple payment gateways (Stripe, PayPal, Square). Each gateway has different initialization requirements, and I want to add new gateways without modifying existing code.”

Must explain:

  1. Product Interface - Common interface for all products
  2. Concrete Products - Specific implementations
  3. Factory - Creates products based on input
  4. Client - Uses factory to get products

Visual explanation:

Client → Factory.create(type) → Returns Product → Concrete Implementation

Benefits to mention:

  • Decoupling - Client doesn’t depend on concrete classes
  • Flexibility - Easy to add new types
  • Centralized Control - All creation logic in one place
  • Testability - Easy to mock factories for testing
  • Follows SOLID - Especially Open/Closed Principle

Trade-offs to acknowledge:

  • Complexity - Adds abstraction layer (may be overkill for simple cases)
  • Performance - Small overhead (usually negligible)
  • Over-engineering risk - Don’t use for trivial cases

Q: “What’s the difference between Factory Pattern and Factory Method Pattern?”

A:

“Factory Pattern (Simple Factory) uses a single method to create objects based on input. Factory Method Pattern is more advanced - each product has its own factory class. Factory Method provides more flexibility and follows the Open/Closed Principle better, but Simple Factory is often sufficient for most cases.”

Q: “When would you NOT use Factory Pattern?”

A:

“I wouldn’t use Factory Pattern when object creation is trivial - like creating a simple object with new Class(). Also, if I only ever create one type of object, a factory is unnecessary. I’d avoid it if performance is critical and the overhead matters, though usually the overhead is negligible.”

Q: “How does Factory Pattern relate to SOLID principles?”

A:

“Factory Pattern primarily supports the Open/Closed Principle - you can add new product types without modifying existing code. It also supports Dependency Inversion Principle by making clients depend on abstractions (the interface) rather than concrete classes. Additionally, it can help with Single Responsibility Principle by separating creation logic from business logic.”

Key implementation points:

  1. Use Abstract Base Class (ABC) for the product interface

    from abc import ABC, abstractmethod
    class Product(ABC):
    @abstractmethod
    def do_something(self): pass
  2. Factory method should return the interface type

    def create(type: str) -> Product: # Return interface, not concrete class
  3. Handle error cases - Invalid types should raise exceptions

    if not product_class:
    raise ValueError(f"Unknown type: {type}")
  4. Consider making factory static - If no state needed

    @staticmethod
    def create(type: str) -> Product:

Good examples to mention:

  • Payment Processing - Different payment gateways
  • Database Connections - Different database types (MySQL, PostgreSQL)
  • UI Components - Different button types (Windows, Mac, Linux)
  • Logging Systems - Different log handlers (File, Console, Database)
  • Notification Systems - Different channels (Email, SMS, Push)

Mistakes interviewers watch for:

  1. Over-engineering - Using Factory for simple cases

    • ❌ Bad: Factory for creating simple objects
    • ✅ Good: Factory for complex, multi-step creation
  2. Returning inconsistent types - Factory should return interface type

    • ❌ Bad: Returning different types (string, int, object)
    • ✅ Good: Always return the interface type
  3. Factory doing too much - Factory should only create, not validate/save/log

    • ❌ Bad: Factory creates, validates, saves, and logs
    • ✅ Good: Factory only creates and returns
  4. Not handling errors - Should validate input and raise exceptions

    • ❌ Bad: Returning None for invalid types
    • ✅ Good: Raising ValueError with clear message

Factory vs Builder:

  • Factory - Creates objects of different types (Margherita vs Pepperoni pizza)
  • Builder - Creates objects with complex construction (Pizza with many toppings)

Factory vs Singleton:

  • Factory - Creates multiple instances of different types
  • Singleton - Ensures only one instance exists

Factory vs Abstract Factory:

  • Factory - Creates one type of product
  • Abstract Factory - Creates families of related products

What interviewers look for:

Clean code - Readable, well-structured
Type hints - Proper type annotations
Error handling - Validates input, raises exceptions
Documentation - Clear docstrings
SOLID principles - Follows design principles
Testability - Easy to test and mock

Example of good code:

from abc import ABC, abstractmethod
from typing import Dict, Any
class PaymentProcessor(ABC):
"""Interface for payment processors"""
@abstractmethod
def process(self, amount: float) -> Dict[str, Any]:
"""Process a payment"""
pass
class PaymentFactory:
"""Factory for creating payment processors"""
@staticmethod
def create(gateway: str, config: Dict[str, str]) -> PaymentProcessor:
"""
Creates a payment processor based on gateway type.
Args:
gateway: Type of payment gateway
config: Configuration dictionary
Returns:
PaymentProcessor instance
Raises:
ValueError: If gateway type is unknown or config is invalid
"""
# Implementation...

Before your interview, make sure you can:

  • Define Factory Pattern clearly in one sentence
  • Explain when to use it (with examples)
  • Describe the structure and components
  • List benefits and trade-offs
  • Compare with other creational patterns
  • Implement Factory Pattern from scratch
  • Connect to SOLID principles
  • Identify when NOT to use it
  • Give 2-3 real-world examples
  • Discuss common mistakes and how to avoid them

Remember: Factory Pattern is about delegating object creation to a specialized factory, making your code more flexible and maintainable! 🏭