Creational Design Patterns
The five GoF creational patterns: when to reach for each, how they’re structured, and what to avoid. All five answer the same question — “how do I create an object?” — but at different levels of complexity and constraint. See the Software Design Principles page for OCP and DIP, which these patterns repeatedly apply.Agent Trigger
Apply when: Deciding how objects get constructed — Factory Method, Abstract Factory, Builder, Prototype, or Singleton. Rule of thumb: Introduce a creational pattern only when construction varies or must be controlled — avoid Singleton unless truly required.Factory Method
Intent: Define a factory method in a superclass; subclasses override it to change what product gets created. When to use:- You need to extend a library/framework by substituting a component (e.g., swap button type without rewriting UI logic).
- The exact type of object to create isn’t known until runtime.
- You want to return cached/pooled objects rather than always constructing fresh ones.
- You have a simple flat set of product types with no inheritance hierarchy — a plain function or map is cleaner.
- Product families need to stay consistent across multiple dimensions (reach for Abstract Factory instead).
- Putting a
switchon type inside the factory method — that’s a parametric factory, not Factory Method. Extract to subclasses or use a registry. - Conflating the creator’s primary responsibility with product creation. The creator has business logic; the factory method is a hook.
Abstract Factory
Intent: Produce families of related objects through a single interface, guaranteeing cross-family compatibility. When to use:- You have a matrix of (product types) × (variants) and need to ensure variant consistency — e.g., all UI elements must be Windows-style or all Mac-style, never mixed.
- You want to swap an entire product family at the injection point without touching client code.
- You’re writing a platform-neutral library that delegates platform specifics to the host.
- You only have one product type to create — use Factory Method.
- Adding a new product type requires touching every concrete factory — the cost grows with every new product dimension.
- Making the factory a Singleton when you need multiple configurations in the same process (e.g., testing).
- Growing the factory interface for every minor product variation — prefer composition or a parameterized product instead.
Builder
Intent: Construct a complex object step by step; the same construction process can produce different representations. When to use:- Object construction requires many optional parameters, producing the “telescoping constructor” smell.
- You need to produce multiple representations from the same step sequence (e.g., a car and its manual).
- Construction steps must run in a controlled order or can be deferred/recursed (e.g., Composite trees).
- The object is simple with 2–3 required fields — just use a constructor or a plain object literal.
- All fields are required; the step-by-step API provides no value over a single constructor call.
- Not calling
reset()between builds — previous state leaks into the next product. - Putting the
build()result type on the builder interface when builders produce incompatible types (it breaks typing). - Skipping the Director when the same step sequence is duplicated across callers — extract it.
Prototype
Intent: Clone an existing configured object rather than constructing and configuring from scratch. When to use:- You receive an object through an interface and need a copy, but don’t know (or can’t depend on) its concrete class.
- Construction is expensive and the object differs only slightly from a known baseline — clone the baseline and mutate.
- You want to eliminate subclasses that exist only to encode configuration variants.
- The object has circular references or holds external resources (DB connections, file handles) — deep clone semantics become tricky.
- The class is simple enough to reconstruct cheaply from a factory.
- Shallow clone when the object contains nested mutable references — child objects are aliased, not independent.
- Forgetting to override
clone()in subclasses — the parent version returns the parent type, silently losing subclass fields.
Singleton
Intent: Ensure exactly one instance of a class exists in the process, with a global access point. When to use:- A single shared resource must be coordinated globally — e.g., a connection pool, a logger, a config store.
- You need stricter control than a global variable provides (the instance can’t be overwritten externally).
- The “global access” need can be satisfied by dependency injection — prefer DI; it keeps the code testable.
- You’d need multiple instances in tests or in different scopes (multi-tenant, per-request). Singleton collapses those.
- The class has meaningful mutable state that different callers should not share.
export const db = new Database(url) is often sufficient and more testable — reserve the class-based Singleton for cases where lazy initialization or explicit control matters.
Anti-patterns:
- Using Singleton as a substitute for proper dependency injection — hides coupling, kills testability.
- Not handling thread-safety in languages where it matters (Java, Go, Rust) — double-checked locking or
sync.Oncerequired. - Treating factory classes (AbstractFactory, Builder) as Singletons without considering test isolation — valid but declare it explicitly.
Comparison: when to reach for each
Evolution path: designs often start with Factory Method, then grow to Abstract Factory when a second product type is needed, then to Builder when construction becomes multi-step.
See also:
- Software Design Principles — OCP and DIP, which these patterns enforce
- Structural Design Patterns — Adapter, Bridge, Decorator, Facade, etc.
- Behavioral Design Patterns — Strategy, Observer, Command, etc.