Skip to main content

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.
When NOT to use:
  • 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).
Structure:
Anti-patterns:
  • Putting a switch on 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.
When NOT to use:
  • 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.
Structure:
Anti-patterns:
  • 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).
When NOT to use:
  • 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.
Structure:
Anti-patterns:
  • 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.
When NOT to use:
  • 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.
Structure:
Anti-patterns:
  • 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).
When NOT to use:
  • 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.
Structure:
For Node.js: module-level 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.Once required.
  • 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: