Loading learning content...
You've studied Factory Method, Abstract Factory, Builder, Prototype, Singleton, and Object Pool. You understand their structure, participants, and individual use cases. But now comes the truly challenging question—when faced with a real design problem, which pattern should you choose?
This isn't an academic exercise. In production systems, the wrong creational pattern choice leads to rigid architectures, excessive complexity, or inability to evolve. The right choice creates elegant, maintainable systems that adapt gracefully to changing requirements.
The patterns we've studied aren't interchangeable alternatives—they solve fundamentally different problems. Yet their superficial similarity (they all create objects) obscures their distinct purposes. This page provides a systematic framework for distinguishing these patterns and selecting the right one for each situation.
By the end of this page, you will understand the core distinctions between all six creational patterns, be able to compare them across multiple dimensions, and recognize the signature characteristics that indicate when each pattern applies. You'll move from knowing patterns individually to understanding them as a cohesive toolkit.
Before comparing patterns, we must crystalize the fundamental intent of each. Patterns often look similar in implementation, but their motivations differ profoundly. Understanding intent is the first filter in pattern selection.
Every creational pattern addresses a specific tension in object creation. Let's examine each pattern's essential purpose:
| Pattern | Core Intent | One-Sentence Summary |
|---|---|---|
| Factory Method | Defer instantiation to subclasses | Let subclasses decide which class to instantiate, enabling framework-level hooks for object creation. |
| Abstract Factory | Create families of related objects | Produce consistent sets of objects that belong together without specifying their concrete classes. |
| Builder | Construct complex objects step-by-step | Separate the construction process from representation, allowing the same process to create different outcomes. |
| Prototype | Create objects by copying existing ones | Avoid the cost of creation from scratch by cloning pre-configured instances. |
| Singleton | Ensure exactly one instance exists | Provide a global point of access to a single instance that coordinates across the system. |
| Object Pool | Reuse expensive objects | Manage a pool of pre-created objects to eliminate repeated creation/destruction overhead. |
When selecting a pattern, start by articulating the problem you're solving. Does it involve deferring instantiation decisions? Creating object families? Step-by-step construction? Cloning? Ensuring uniqueness? Reusing expensive resources? The answer immediately narrows your options.
The problem-to-pattern mapping:
The key insight is that each pattern answers a different question:
If you can phrase your problem as one of these questions, you've identified the pattern.
Three of our patterns—Factory Method, Abstract Factory, and Builder—address situations where what gets created or how it's created varies. Understanding the nature of this variability is crucial for choosing between them.
Factory Method deals with variability in the type of object created. The creation logic is fixed, but the specific class instantiated depends on the subclass implementing the factory method.
Abstract Factory addresses variability in families of objects. When your system must work with sets of related objects (e.g., UI widgets for different platforms), Abstract Factory ensures you get a consistent family.
Builder handles variability in the construction process itself. When objects have many optional components or require complex assembly, Builder separates this complexity from the object's representation.
If you're creating one object and the type varies, consider Factory Method. If you're creating multiple related objects that must be compatible, consider Abstract Factory. If construction is complex with many optional steps, consider Builder.
Three patterns—Prototype, Singleton, and Object Pool—are fundamentally about reusing objects rather than creating new ones from scratch. However, they differ in what is reused and why.
Prototype reuses an existing object's configuration. When creating an object from scratch is expensive (due to complex initialization, resource loading, or extensive configuration), cloning a pre-configured prototype is faster.
Singleton reuses a single instance across the entire application. The goal isn't performance—it's ensuring that all parts of the system work with the same object, maintaining consistency.
Object Pool reuses multiple instances to amortize creation cost over time. Unlike Singleton (one instance, shared) or Prototype (many clones, independent), Object Pool maintains a bounded set of instances that are checked out, used, and returned.
| Dimension | Prototype | Singleton | Object Pool |
|---|---|---|---|
| Number of instances | Unlimited (via cloning) | Exactly one | Bounded pool |
| Reuse mechanism | Clone configuration | Share single instance | Borrow and return |
| Primary motivation | Avoid expensive initialization | Ensure global consistency | Amortize creation/destruction cost |
| Client relationship | Each client owns their clone | All clients share one instance | Clients borrow temporarily |
| State isolation | Clones are independent | State is shared (careful!) | State reset between uses |
| Thread safety concern | Clones are isolated | High (shared mutable state) | Medium (pool access, reset) |
Choosing between reuse patterns:
The selection criteria are clear once you understand the core differences:
Prototype: Use when initialization is expensive, but each client needs their own independent copy. Example: Game objects with complex texture/mesh loading.
Singleton: Use when there must be exactly one instance and all parts of the system need access to it. Example: Configuration manager, logging facade.
Object Pool: Use when creation/destruction is expensive and you don't need simultaneous unbounded instances. Example: Database connections, thread workers, socket connections.
Singleton is often overused. If your motivation is 'I want global access' rather than 'I need exactly one instance,' you're likely misusing the pattern. Consider dependency injection instead. Singleton creates hidden dependencies and makes testing difficult.
Patterns differ significantly in their structural complexity and the number of participating classes. This matters for two reasons: simpler patterns are easier to implement and maintain, but more complex patterns handle more sophisticated scenarios. Choosing the simplest pattern that solves your problem is a key design principle.
Let's rank the patterns by structural complexity:
Structure drives evolution cost:
The structural complexity of a pattern determines how much code changes when you extend it:
| Pattern | To Add New Product Type |
|---|---|
| Factory Method | Add new creator subclass + product class |
| Abstract Factory | Add methods to all factories + new product classes |
| Builder | Add new builder + modify director (if used) |
| Prototype | Add new cloneable product to registry |
| Singleton | N/A (only one instance) |
| Object Pool | Modify pooled object class or pool configuration |
Guideline: Choose the simplest pattern that meets your needs. Don't use Abstract Factory when Factory Method suffices. Don't use Builder when a simple constructor works.
Just because Abstract Factory is more powerful doesn't mean you should reach for it first. If you only have one product type that varies, Factory Method is sufficient. If construction is simple, skip Builder entirely. Pattern complexity should match problem complexity.
A fundamental distinction among creational patterns is whether they rely primarily on inheritance (subclassing) or composition (object relationships). This significantly affects flexibility, testability, and evolution.
Inheritance-based patterns require you to create subclasses to vary behavior. This provides compile-time type safety but creates class hierarchies that can become rigid.
Composition-based patterns rely on delegating to helper objects. This provides runtime flexibility and is generally preferred for modern designs, but requires more careful interface design.
| Pattern | Primary Mechanism | Implications |
|---|---|---|
| Factory Method | Inheritance | Subclass per product type; parallel hierarchies can emerge |
| Abstract Factory | Composition | Inject factory; easy to swap families at runtime |
| Builder | Composition | Inject builder; director orchestrates without knowing builder type |
| Prototype | Composition (with delegation) | Clone existing object; no class hierarchy required |
| Singleton | Neither (structural) | Single class with controlled instantiation |
| Object Pool | Composition | Pool manages object lifecycle independently |
Modern software design strongly favors composition over inheritance. Factory Method (inheritance-based) is often evolved into Abstract Factory or simple parameterized factories in contemporary codebases. If you're choosing between patterns, prefer composition-based approaches unless inheritance provides clear benefits.
Creational patterns aren't isolated islands—they have relationships and evolutionary paths. Understanding these relationships helps you evolve designs gracefully as requirements change.
Patterns often start simple and evolve to more complex forms:
Factory Method → Abstract Factory: You start with Factory Method for one product type. As you add more related products that must be consistent, you evolve to Abstract Factory.
Simple Constructor → Builder: You start with a constructor. As optional parameters multiply, you add telescoping constructors, then finally evolve to Builder.
new() Calls → Prototype: You start creating objects directly. When initialization becomes expensive or configuration-heavy, you introduce Prototype.
Static Instance → Singleton → Dependency Injection: You start with a global instance. You formalize it as Singleton. You eventually recognize the problems and evolve toward DI.
123456789101112131415161718192021222324252627282930313233343536373839
// Evolution: Constructor → Telescoping → Builder // Stage 1: Simple constructorclass Pizza { constructor(size: string, cheese: boolean, pepperoni: boolean, mushrooms: boolean, onions: boolean, bacon: boolean) { // Painful for callers: new Pizza("large", true, false, true, false, true) }} // Stage 2: Telescoping constructors (still awkward)class PizzaV2 { constructor(size: string, cheese?: boolean, pepperoni?: boolean, mushrooms?: boolean, onions?: boolean, bacon?: boolean) { // Slightly better, but order still matters }} // Stage 3: Builder patternclass PizzaBuilder { private size: string = "medium"; private toppings: Set<string> = new Set(); setSize(size: string): this { this.size = size; return this; } addTopping(topping: string): this { this.toppings.add(topping); return this; } build(): Pizza { return new Pizza(/* ... */); }} // Clean: new PizzaBuilder().setSize("large").addTopping("cheese").build()Pattern substitution relationships:
Some patterns can substitute for others in specific contexts:
| Instead of... | You might use... | When? |
|---|---|---|
| Factory Method | Simple factory function | When inheritance hierarchy is overkill |
| Abstract Factory | Dependency injection | When you want even looser coupling |
| Prototype | Factory with configuration | When cloning semantics aren't needed |
| Singleton | Scoped instance via DI | When global access isn't required |
| Object Pool | On-demand creation | When objects aren't actually expensive |
Don't over-engineer upfront. Start with the simplest solution (maybe just constructor calls or factory functions). Evolve to formal patterns when complexity genuinely warrants it. The evolutionary paths above are natural progressions—use them when needed, not preemptively.
Let's consolidate everything into a comprehensive comparison matrix. This serves as a reference when evaluating which pattern fits your situation.
| Criterion | Factory Method | Abstract Factory | Builder | Prototype | Singleton | Object Pool |
|---|---|---|---|---|---|---|
| Primary purpose | Defer type to subclass | Create product families | Step-by-step construction | Clone existing objects | Single global instance | Reuse expensive objects |
| When to use | Type varies by subclass | Need compatible object sets | Complex construction | Expensive initialization | Exactly one instance | Creation/destruction cost |
| Mechanism | Inheritance | Composition | Composition | Composition | Static access | Composition |
| Flexibility | Medium | High | High | High | Low | Medium |
| Testability | Medium | High | High | High | Low | Medium |
| Complexity | Low-Medium | Medium-High | Medium | Low | Low | Medium |
| Instance count | New per call | New per call | New per build | Clone per request | Exactly one | Pooled/bounded |
| Common misuse | Over-subclassing | Used for single product | Simple objects | Ignoring deep copy | Overuse for global state | Non-expensive objects |
Bookmark this comparison. When facing a creational design decision, review the matrix to quickly narrow down candidates based on your specific criteria—primary purpose, flexibility needs, testability requirements, and acceptable complexity.
We've systematically compared all six creational patterns across multiple dimensions. Let's consolidate the key insights:
What's next:
With a solid understanding of how patterns compare, the next page presents a decision tree—a systematic flowchart for selecting the right creational pattern. You'll learn to navigate from problem characteristics to pattern choice through a series of decisive questions.
You now understand how creational patterns compare across intent, variability, reuse, complexity, and mechanism. You can distinguish between similar-looking patterns and understand their evolutionary relationships. Next, we'll build a decision framework for pattern selection.