Creational Patterns: Factory, Builder & Singleton
Factory Method, Abstract Factory, Builder and Singleton — what problem each solves and when to avoid them.
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.
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:
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:
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:
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.
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:
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.
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:
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.