Creational Patterns
In a Nutshell
Creational design patterns are proven solutions to a recurring question in software: how should objects be created? Naively, you just call a constructor (new Thing()), but that hard-wires your code to a specific class and creation logic, making it rigid and hard to change. Creational patterns add a layer of indirection to object creation so your code depends on abstractions rather than concrete classes — making systems more flexible, testable, and extensible. The classic patterns — Singleton, Factory, Abstract Factory, Builder, and Prototype — each solve a specific creation problem. In system design, they show up constantly in how you construct clients, connections, configurations, and complex objects.

How It Actually Works
The Problem Creational Patterns Solve
Direct construction couples your code to concrete classes:
order.payment = new StripePaymentProcessor() // hard-wired to Stripe
Now switching to PayPal, testing with a mock, or choosing a processor at
runtime all require editing this code. Creational patterns decouple WHAT
you need from HOW it's created:
order.payment = PaymentFactory.create(config.provider) // flexible
The Five Classic Creational Patterns
| Pattern | Problem It Solves | One-Liner |
|---|---|---|
| Singleton | Exactly one shared instance needed | Global single instance with controlled access |
| Factory Method | Creating objects without naming the concrete class | A method decides which class to instantiate |
| Abstract Factory | Creating families of related objects | A factory that produces related factories/objects |
| Builder | Constructing complex objects step by step | Assemble a complex object piece by piece |
| Prototype | Creating objects by copying an existing one | Clone instead of construct from scratch |
Singleton — One Instance
Ensures a class has only one instance with a global access point. Common for shared resources: a database connection pool, a config manager, a logger.
class ConfigManager:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._load()
return cls._instance
# Every ConfigManager() returns the SAME instance.
The Singleton caution: it's the most overused and criticized pattern — it's essentially global state, which hurts testability (hard to mock/reset), can hide dependencies, and causes issues in concurrent/distributed contexts. Prefer dependency injection where possible; use Singleton deliberately, not by habit.
Factory — Decouple Creation from Use
A factory method or class decides which concrete object to create, so callers depend on an interface, not a concrete class:
class PaymentFactory:
@staticmethod
def create(provider):
if provider == "stripe": return StripeProcessor()
if provider == "paypal": return PayPalProcessor()
raise ValueError(provider)
processor = PaymentFactory.create(config.provider) # caller doesn't know the class
processor.charge(amount) # just uses the interface
Adding a new provider means adding a case in the factory — callers never change. This is the open/closed principle (see LLD & SOLID) in action.
Builder — Assemble Complex Objects
When an object has many optional parameters or a multi-step construction, a builder avoids "telescoping constructors" (constructors with a dozen arguments):
query = (QueryBuilder()
.select("id", "name")
.from_table("users")
.where("age > 18")
.order_by("name")
.limit(20)
.build()) # readable, flexible, each step optional
Builders are everywhere: HTTP request builders, query builders, configuration objects.
Where They Show Up in System Design
| Pattern | Real-World Use |
|---|---|
| Singleton | Connection pools, config, logger, cache client |
| Factory | Choosing a DB driver / payment provider / storage backend by config |
| Abstract Factory | Cross-platform UI kits, multi-cloud provider families |
| Builder | Query builders, request builders, complex config objects |
| Prototype | Cloning expensive-to-create objects, object pools |

Seeing It in Action
Scenario: Using creational patterns to build a flexible payment system.
# FACTORY — choose the processor by config, decoupled from concrete classes
class PaymentFactory:
_registry = {"stripe": StripeProcessor, "paypal": PayPalProcessor}
@classmethod
def create(cls, provider):
return cls._registry[provider]() # add a provider → register it, callers unchanged
# SINGLETON — one shared connection pool for the whole app (a real, valid use)
class DBPool:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.pool = create_pool(size=20) # expensive; do ONCE
return cls._instance
# BUILDER — assemble a complex payment request step by step
payment = (PaymentRequest.builder()
.amount(59.99).currency("USD")
.customer("cust_42")
.idempotency_key("order-7") # optional, retry-safe
.metadata({"order_id": 7}) # optional
.build())
# Putting it together — flexible, testable, extensible:
processor = PaymentFactory.create(config.payment_provider) # Factory
db = DBPool() # Singleton (shared pool)
result = processor.charge(payment) # Builder-made request
# Why each pattern earns its place:
# Factory: switch providers or add new ones via config, no caller changes;
# tests inject a MockProcessor by registering it.
# Singleton: ONE 20-connection pool shared app-wide (not one per request) —
# a legitimate use for a genuinely shared, expensive resource.
# Builder: a payment request with many optional fields stays readable and
# valid, instead of a constructor with 8 positional arguments.
Why creational patterns matter in real systems: the naive approach of scattering new ConcreteClass() calls throughout your code seems simplest, but it quietly hard-wires every decision — which payment provider, which database driver, which storage backend — into the call sites, so changing any of them means hunting down and editing code everywhere, and testing means you can't substitute a mock. Creational patterns add a deliberate seam at the point of creation: the Factory lets you choose or swap implementations by configuration (and inject test doubles) without touching callers, embodying the open/closed principle; the Builder keeps the construction of objects with many optional parts readable and correct instead of collapsing into unreadable mega-constructors; and the Singleton — used judiciously — ensures a genuinely shared, expensive resource like a connection pool is created exactly once rather than wastefully per request. The key judgment is not to cargo-cult these patterns (especially Singleton, which is really global state and often better replaced by dependency injection) but to reach for them when creation logic is a real source of coupling or complexity. Applied deliberately, they make a system flexible (swap implementations), testable (inject mocks), and extensible (add new types without editing existing code) — which is exactly what you want when requirements inevitably change.
Interview Questions
Q: What problem do creational patterns solve in general? Hint: Direct construction (
new ConcreteClass()) hard-wires code to specific classes and creation logic, making it rigid, hard to test (can't substitute mocks), and hard to change (edits scattered across call sites). Creational patterns add indirection at the point of object creation so code depends on abstractions/interfaces rather than concrete classes — yielding flexibility (swap implementations), testability, and extensibility (add types without editing callers).Q: What is the Factory pattern and what principle does it support? Hint: A factory method/class decides which concrete object to instantiate, so callers depend on an interface and pass a config/type rather than naming the class. Adding a new type means extending the factory; callers never change — supporting the open/closed principle (open for extension, closed for modification). It also enables injecting mocks for testing and choosing implementations at runtime.
Q: Why is Singleton controversial, and when is it appropriate? Hint: It's essentially global state — it hurts testability (hard to mock/reset), hides dependencies (callers don't declare them), and causes problems in concurrent/distributed contexts. It's the most overused pattern. Appropriate for genuinely shared, single, expensive resources (connection pools, config, logger) — but prefer dependency injection where possible and use it deliberately, not by habit.
Q: When would you use the Builder pattern? Hint: When constructing an object that has many (often optional) parameters or requires a multi-step assembly, to avoid "telescoping constructors" with many positional arguments that are unreadable and error-prone. The builder assembles the object step by step with named, optional steps (query builders, HTTP request builders, complex config objects), producing readable, flexible, valid construction.
Q: Give a real system-design example for Factory and for Singleton. Hint: Factory: selecting a database driver, payment provider, or storage backend based on configuration, so the app can switch or add providers without changing callers. Singleton: a shared database connection pool, configuration manager, logger, or cache client that should exist exactly once app-wide rather than being recreated per request — a legitimate use for a genuinely shared, expensive resource.
References
- Design Patterns (Gang of Four) — the original patterns catalog
- Refactoring.Guru: Creational Patterns — clear explanations with examples
- SOLID / LLD (Topic 02) — the principles patterns support
Dive Deeper
- Head First Design Patterns — approachable, example-driven
- Singleton considered harmful (dependency injection alternatives) by Martin Fowler — DI over Singleton
- Effective Java by Joshua Bloch — builders and factories done right