Design Patterns Lesson 3 of 6 ⏱️ 5 min read

Creational Patterns: Factory, Builder & Singleton

Factory Method, Abstract Factory, Builder and Singleton — what problem each solves and when to avoid them.

Updated Oct 30, 2026

Why creational patterns?

Calling a constructor directly (StripeGateway()) spreads knowledge of concrete classes all over your code. When you add a new payment provider or need a complex object with many options, every caller has to change. Creational patterns centralise and simplify object creation.

Pattern Use it when…
Factory Method You need to create one of several related types, chosen at runtime
Abstract Factory You need families of related objects that must be used together
Builder An object has many optional parts or must be built step by step
Singleton Exactly one shared instance must exist (use sparingly)

Factory Method

A factory decides which concrete class to instantiate, so callers depend only on a common interface.

Python
from typing import Protocol

class PaymentGateway(Protocol):
    def charge(self, amount: int) -> str: ...

class StripeGateway:
    def charge(self, amount): return f"Stripe charged {amount}"

class JazzCashGateway:
    def charge(self, amount): return f"JazzCash charged {amount}"

class EasypaisaGateway:
    def charge(self, amount): return f"Easypaisa charged {amount}"

GATEWAYS = {
    "stripe": StripeGateway,
    "jazzcash": JazzCashGateway,
    "easypaisa": EasypaisaGateway,
}

def create_gateway(name: str) -> PaymentGateway:
    try:
        return GATEWAYS[name]()
    except KeyError:
        raise ValueError(f"Unknown gateway: {name}") from None

for name in ("stripe", "jazzcash"):
    print(create_gateway(name).charge(2500))

Output:

Plaintext
Stripe charged 2500
JazzCash charged 2500

The checkout code calls create_gateway(config["payment_gateway"]) and never mentions concrete classes. Adding a provider means adding a class and a registry entry.

The classic GoF Factory Method uses a subclass to decide what to create (Dialog.create_button() overridden by WindowsDialog and WebDialog); the registry-function version above is the idiomatic Python form of the same idea.

Abstract Factory

An abstract factory creates families of related objects that must match. For example, a storage layer where a cloud provider's file storage and queue must come from the same vendor:

Python
class AWSFactory:
    def storage(self): return "S3 bucket"
    def queue(self): return "SQS queue"

class GCPFactory:
    def storage(self): return "Cloud Storage bucket"
    def queue(self): return "Pub/Sub topic"

def build_infrastructure(factory) -> str:
    return f"{factory.storage()} + {factory.queue()}"

print(build_infrastructure(AWSFactory()))
print(build_infrastructure(GCPFactory()))

Output:

Plaintext
S3 bucket + SQS queue
Cloud Storage bucket + Pub/Sub topic

Swapping the factory swaps the whole family consistently — you can't accidentally mix S3 with Pub/Sub.

Builder

A builder constructs a complex object step by step, avoiding constructors with a dozen parameters ("telescoping constructors"). Methods return self, giving a readable fluent interface — the same style as ORM query builders.

Python
class QueryBuilder:
    def __init__(self, table: str):
        self._table = table
        self._wheres: list[str] = []
        self._order = ""
        self._limit = ""

    def where(self, condition: str) -> "QueryBuilder":
        self._wheres.append(condition)
        return self

    def order_by(self, column: str, direction: str = "ASC") -> "QueryBuilder":
        self._order = f" ORDER BY {column} {direction}"
        return self

    def limit(self, n: int) -> "QueryBuilder":
        self._limit = f" LIMIT {n}"
        return self

    def build(self) -> str:
        sql = f"SELECT * FROM {self._table}"
        if self._wheres:
            sql += " WHERE " + " AND ".join(self._wheres)
        return sql + self._order + self._limit

query = (QueryBuilder("courses")
         .where("level = 'beginner'")
         .where("price < 5000")
         .order_by("rating", "DESC")
         .limit(10)
         .build())
print(query)

Output:

Plaintext
SELECT * FROM courses WHERE level = 'beginner' AND price < 5000 ORDER BY rating DESC LIMIT 10

(A real query builder would use bound parameters instead of string conditions — see the Backend Security tutorial.)

In Python, keyword arguments with defaults and dataclasses often remove the need for a builder for simple objects. Builders shine for step-by-step construction, validation before building, and fluent APIs.

Singleton

A singleton ensures a class has one instance and provides global access to it — for example, application configuration or a connection pool.

Python
class Config:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.settings = {"env": "production"}
        return cls._instance

a = Config()
b = Config()
b.settings["debug"] = False
print(a is b, a.settings)

Output:

Plaintext
True {'env': 'production', 'debug': False}

In Python, a module is already a singleton — import config returns the same module object everywhere — so a module-level instance is usually simpler.

Why singletons are controversial

  • They are global state in disguise: any code can change them, making bugs hard to trace.
  • They make testing hard — tests share state and can't easily substitute a fake.
  • In multi-threaded code, lazy creation needs locking.

Prefer creating one instance at startup and injecting it (dependency injection). Frameworks like Laravel and Spring manage "singleton-scoped" services this way — one instance, but still replaceable in tests.

Other creational patterns

  • Prototype — create new objects by cloning an existing one (copy.deepcopy).
  • Object pool — reuse expensive objects such as database connections instead of creating new ones.

Common mistakes

  • Factories for classes that never vary — just call the constructor.
  • Singletons for convenience, turning the codebase into hidden global variables.
  • Builders for objects with two fields — use keyword arguments.

Key takeaways

  • Factories hide concrete classes behind an interface chosen at runtime.
  • Abstract factories create consistent families of related objects.
  • Builders construct complex objects step by step with a readable fluent API.
  • Singletons guarantee one instance but act like global state — prefer dependency injection.
Finished this lesson?
Tick it off to track your Design Patterns progress.
Next lesson