Chapter 03 · Article 13 of 55

Prototype Pattern

Intent: Create new objects by cloning existing ones (prototypes) rather than constructing them from scratch.

Article outline16 sections on this page

Overview

Intent: Create new objects by cloning existing ones (prototypes) rather than constructing them from scratch.

Category: Creational Design Pattern

Also Known As: Clone Pattern

The Prototype pattern delegates the object creation process to the objects themselves. Each object that supports cloning implements a clone() method, allowing clients to produce new instances without coupling to their concrete classes. The original object acts as a prototype - a blueprint that can be duplicated and customized independently.

Core Principle: When object creation is expensive or complex, copy an existing configured instance instead of building one from zero.


Problem It Solves

  1. Expensive Object Creation - Objects requiring heavy computation, database queries, network calls, or file I/O during initialization. Cloning bypasses the costly setup entirely.

  2. Complex Initialization Logic - Objects with dozens of configuration parameters, nested dependencies, or multi-step setup sequences. A pre-configured prototype eliminates repetitive initialization code.

  3. Avoiding Subclass Proliferation - Without Prototype, you might create a new subclass for every variation of an object. Cloning a prototype and tweaking fields avoids an explosion of classes.

  4. Runtime Type Flexibility - When the exact type of object to create is determined at runtime and you want to avoid large if/else or switch blocks tied to concrete classes.

  5. Preserving Object State - When you need a snapshot of an object at a particular point in time (undo systems, versioning).


When to Use / When NOT to Use

When to UseWhen NOT to Use
Object creation is resource-intensive (DB, network, computation)Objects are cheap to construct via new
System should be independent of how objects are created/composedClass hierarchy is simple and stable
Objects differ only in state, not structureObjects have no meaningful shared initial state
You need runtime configuration of object typesDeep copy complexity outweighs creation cost
Avoiding parallel class hierarchies (factory per type)Objects hold non-cloneable resources (live sockets, threads)
Undo/snapshot functionality is neededImmutable objects (just share the reference)
Prototype registry can reduce class countCircular dependencies make cloning error-prone

Key Concepts & Theory

Shallow Copy vs Deep Copy

  • Shallow Copy: Copies field values directly. Primitive fields are duplicated; reference fields still point to the same objects in memory. Changes to nested objects affect both original and clone.
  • Deep Copy: Recursively copies all objects in the graph. The clone is fully independent - no shared mutable state with the original.

Prototype Registry (Prototype Manager)

A centralized store (often a HashMap<String, Prototype>) holding pre-configured prototype instances. Clients request clones by key rather than knowing concrete classes. This decouples client code from specific types entirely.

Cloneable Interface

A contract (e.g., Java's Cloneable, or a custom Prototype interface) declaring the clone() method. Every concrete prototype implements this, encapsulating its own duplication logic.

Copy Constructors

An alternative to clone() - a constructor that accepts an instance of the same class and copies its fields. Avoids issues with Java's broken Cloneable contract and gives explicit control over the copy process.

class Shape {
    Shape(Shape source) {
        this.x = source.x;
        this.y = source.y;
        this.color = new Color(source.color); // deep copy
    }
}

ASCII Class Diagram

┌─────────────────────────────┐
│         <<interface>>       │
│          Prototype          │
├─────────────────────────────┤
│ + clone(): Prototype        │
└──────────────┬──────────────┘
               │ implements
       ┌───────┴────────┐
       │                 │
┌──────▼───────┐  ┌─────▼────────┐
│ Concrete     │  │ Concrete     │
│ Prototype1   │  │ Prototype2   │
├──────────────┤  ├──────────────┤
│ - field1     │  │ - fieldA     │
│ - field2     │  │ - fieldB     │
├──────────────┤  ├──────────────┤
│ + clone()    │  │ + clone()    │
└──────────────┘  └──────────────┘
       ▲                 ▲
       │    requests     │
       │    clone()      │
┌──────┴─────────────────┴─────┐
│           Client             │
├──────────────────────────────┤
│ - prototypes: Map<Key, Proto>│
├──────────────────────────────┤
│ + makeObject(key): Prototype │
└──────────────────────────────┘

Participants:

  • Prototype - declares the clone() interface
  • ConcretePrototype - implements clone() to return a copy of itself
  • Client - creates new objects by asking a prototype to clone itself

Pseudocode Implementation

Example 1: Shape Cloning (Shallow vs Deep Copy)

// Nested value object
class Color:
    red: int
    green: int
    blue: int

    constructor Color(r, g, b):
        this.red = r
        this.green = g
        this.blue = b

    method deepCopy() -> Color:
        return new Color(this.red, this.green, this.blue)

// Prototype interface
interface Shape:
    method clone() -> Shape          // shallow
    method deepClone() -> Shape      // deep

// Concrete Prototype: Circle
class Circle implements Shape:
    x: int
    y: int
    radius: int
    color: Color                     // reference type (mutable)

    constructor Circle(x, y, radius, color):
        this.x = x
        this.y = y
        this.radius = radius
        this.color = color

    // SHALLOW COPY  -  color is shared
    method clone() -> Shape:
        copy = new Circle(this.x, this.y, this.radius, this.color)
        return copy

    // DEEP COPY  -  color is duplicated
    method deepClone() -> Shape:
        copy = new Circle(this.x, this.y, this.radius, this.color.deepCopy())
        return copy

// Concrete Prototype: Rectangle
class Rectangle implements Shape:
    width: int
    height: int
    color: Color

    method clone() -> Shape:
        return new Rectangle(this.width, this.height, this.color)

    method deepClone() -> Shape:
        return new Rectangle(this.width, this.height, this.color.deepCopy())

// Client usage
original = new Circle(10, 20, 5, new Color(255, 0, 0))

shallow = original.clone()
shallow.color.green = 128          //  ALSO changes original.color!

deep = original.deepClone()
deep.color.blue = 255              //  Only affects the deep copy

Example 2: Document Template System

class Paragraph:
    text: string
    fontSize: int

    method deepCopy() -> Paragraph:
        return new Paragraph(this.text, this.fontSize)

class Document implements Prototype:
    title: string
    author: string
    paragraphs: List<Paragraph>
    metadata: Map<string, string>
    createdAt: DateTime

    // Copy constructor approach
    constructor Document(source: Document):
        this.title = source.title
        this.author = source.author
        this.createdAt = DateTime.now()          // fresh timestamp
        // Deep copy the list of paragraphs
        this.paragraphs = []
        for p in source.paragraphs:
            this.paragraphs.add(p.deepCopy())
        // Deep copy the metadata map
        this.metadata = new Map(source.metadata)

    method clone() -> Document:
        return new Document(this)

// --- Prototype Registry ---
class TemplateRegistry:
    templates: Map<string, Document> = {}

    method register(key: string, doc: Document):
        templates[key] = doc

    method create(key: string) -> Document:
        if key not in templates:
            throw "Template not found: " + key
        return templates[key].clone()

// --- Usage ---
// Setup: register templates once
invoice = new Document("Invoice Template", "System", [...], {...})
report  = new Document("Quarterly Report", "System", [...], {...})

registry = new TemplateRegistry()
registry.register("invoice", invoice)
registry.register("report", report)

// Runtime: clone and customize
myInvoice = registry.create("invoice")
myInvoice.title = "Invoice #2024-0042"
myInvoice.author = "Shivam"
myInvoice.metadata["client"] = "Acme Corp"
// Original template remains unchanged

Shallow Copy vs Deep Copy

Comparison Table

AspectShallow CopyDeep Copy
MechanismCopies field values as-isRecursively copies entire object graph
PrimitivesDuplicated (independent)Duplicated (independent)
Reference fieldsShared with originalNew independent copies
PerformanceFast - O(1) per reference fieldSlower - O(n) where n = graph size
MemoryLower (shared references)Higher (full duplication)
IndependencePartial - nested mutations propagateFull - completely independent
ComplexitySimple to implementMust handle cycles, special types
Use caseImmutable nested objects, read-only sharingMutable nested objects, full isolation

Memory Reference Diagram

SHALLOW COPY:
┌──────────────┐         ┌──────────────┐
│   Original   │         │    Clone     │
├──────────────┤         ├──────────────┤
│ x: 10       │         │ x: 10       │  ← independent (primitive)
│ y: 20       │         │ y: 20       │  ← independent (primitive)
│ color: ─────┼────┐    │ color: ─────┼────┐
└──────────────┘    │    └──────────────┘    │
                    │                         │
                    └──────►┌────────┐◄───────┘
                            │ Color  │  ← SHARED object!
                            │ r: 255 │
                            │ g: 0   │
                            └────────┘

DEEP COPY:
┌──────────────┐         ┌──────────────┐
│   Original   │         │    Clone     │
├──────────────┤         ├──────────────┤
│ x: 10       │         │ x: 10       │
│ y: 20       │         │ y: 20       │
│ color: ─────┼──┐      │ color: ─────┼──┐
└──────────────┘  │      └──────────────┘  │
                  ▼                         ▼
            ┌────────┐               ┌────────┐
            │ Color  │               │ Color  │  ← INDEPENDENT copy
            │ r: 255 │               │ r: 255 │
            │ g: 0   │               │ g: 0   │
            └────────┘               └────────┘

Real-World Examples

1. Object Pools (Connection/Thread Pools)

Pre-configured connection objects are cloned from a prototype rather than negotiating new connections each time. The prototype holds default timeout, encoding, and retry settings.

2. Game Entity Spawning

A game loads enemy archetypes (health, speed, sprite, AI behavior) once. Spawning 1000 enemies clones the archetype prototype and randomizes position - far cheaper than re-parsing config files per entity.

3. Spreadsheet Cell Copying

When you copy-paste cells in Excel/Google Sheets, the cell object (value, formula, formatting, validation rules) is cloned. Relative references are adjusted post-clone.

4. Configuration Templates

DevOps tools clone a base server configuration prototype, then override environment-specific values (ports, secrets, feature flags). Terraform/Pulumi resource definitions work similarly.

5. JavaScript's Prototype Chain

Object.create(proto) literally implements this pattern at the language level. New objects delegate property lookups to their prototype, achieving memory-efficient sharing with per-instance overrides.

6. Java's Object.clone() and Spring Bean Scopes

Spring's prototype scope creates a new bean instance per request by cloning a template definition - contrasted with singleton scope.


Prototype vs Factory vs Builder

CriteriaPrototypeFactory MethodBuilder
IntentClone existing instanceDelegate creation to subclassesConstruct complex objects step-by-step
When to useObject creation is expensive; many similar objects neededClient doesn't know exact class to instantiateObject has many optional parameters or construction steps
Object sourceExisting instance (runtime)Class hierarchy (compile-time)Accumulated configuration
FlexibilityVery high - any configured stateMedium - limited to class variantsHigh - any combination of parts
ComplexityLow (just clone)Medium (class per product)Medium-High (director + builder)
AvoidsExpensive re-initializationDirect new couplingTelescoping constructors
Typical participantsPrototype, ConcretePrototype, ClientCreator, ConcreteCreator, ProductBuilder, ConcreteBuilder, Director, Product
Combines well withRegistry, CompositeAbstract Factory, Template MethodPrototype (for default values), Fluent interfaces

Rule of thumb: Use Factory when you need different types, Builder when you need different configurations, and Prototype when you need copies of existing state.


Advantages & Disadvantages

AdvantagesDisadvantages
Avoids costly initialization - clone is typically O(1) to O(n)Deep copy can be complex for large object graphs
Reduces subclass proliferationCircular references require special handling
Hides concrete classes from clientObjects with final/immutable fields can't be modified post-clone
Adds/removes products at runtime via registryEach class must implement clone() - maintenance overhead
Preserves object state as a snapshotCloning objects with external resources (DB connections) is dangerous
Works with Composite/Decorator for complex structuresLanguage support varies (Java's Cloneable is notoriously broken)
Simplifies object creation in dynamic systemsShallow copy bugs are subtle and hard to debug

Constraints & Edge Cases

Circular References in Deep Copy

When object A references B and B references A, naive recursive deep copy enters infinite recursion.

Solution: Maintain a visited map (Map<ObjectId, ClonedObject>) during the copy traversal. Before cloning a reference, check if it's already been copied.

method deepCopy(visited: Map<ObjectId, Object>) -> Object:
    if this.id in visited:
        return visited[this.id]       // return already-cloned reference
    clone = new ThisClass()
    visited[this.id] = clone          // register before recursing
    clone.neighbor = this.neighbor.deepCopy(visited)
    return clone

Cloning Objects with External Resources

  • DB Connections: Never clone a live connection. Clone the configuration and open a new connection.
  • File Handles: Clone the file path/metadata, not the OS handle. Open a fresh handle in the clone.
  • Thread/Lock State: Non-cloneable. Mark such fields as transient/excluded from cloning.
  • Event Listeners: Decide whether listeners should transfer to the clone (usually not).

Immutable Fields

Fields declared final (Java) or readonly (C#) cannot be set after construction. Solutions:

  1. Use copy constructors instead of field-by-field assignment.
  2. Use reflection (fragile, last resort).
  3. Design immutable objects to be shared rather than cloned.

Identity and Equality

Clones typically need new identity (new ID, new UUID) but equal content. Ensure clone() resets identity fields while preserving value fields. Override equals() and hashCode() accordingly.


Creational Patterns Summary Comparison

Since this is the final article in the Creational Patterns section, here is a comprehensive side-by-side comparison of all five GoF creational patterns:

AspectSingletonFactory MethodAbstract FactoryBuilderPrototype
IntentEnsure one instance globallyDefer instantiation to subclassesCreate families of related objectsConstruct complex objects step-by-stepClone existing instances
ComplexityLowMediumHighMedium-HighLow-Medium
Primary Use CaseShared resource (config, logger, pool)Framework extension pointsCross-platform UI, themed componentsObjects with many optional partsExpensive creation, runtime type selection
Number of Classes12+ (creator + products)4+ (factories + products)3+ (builder + director + product)2+ (prototype + concretes)
Key ParticipantsSingleton classCreator, ConcreteCreator, ProductAbstractFactory, ConcreteFactory, AbstractProductBuilder, ConcreteBuilder, Director, ProductPrototype, ConcretePrototype, Client/Registry
Object SourcePrivate constructor + static accessSubclass decidesFactory family decidesStep-by-step accumulationExisting configured instance
FlexibilityNone (one instance)Medium (subclass per variant)High (swap entire families)Very High (any combination)Very High (any runtime state)
CouplingGlobal state couplingDecouples client from concrete productDecouples client from product familiesDecouples construction from representationDecouples client from concrete classes
When to AvoidTesting, concurrency issuesSimple creation logicOnly one product family existsSimple objects with few paramsCheap objects, circular refs
Related PatternsFactory Method (often returns singleton)Abstract Factory, Template Method, PrototypeFactory Method (implements each method), SingletonPrototype (default values), CompositeFactory (registry), Composite, Decorator
Real-World AnalogyGovernment (one president)Pizza store (each branch makes local style)IKEA (furniture families per style)Subway sandwich (choose each ingredient)Photocopier (duplicate existing document)

Pattern Selection Flowchart:

Need to create objects?
├── Need exactly ONE instance? → Singleton
├── Need families of related objects? → Abstract Factory
├── Need to vary the type created? → Factory Method
├── Need complex multi-step construction? → Builder
└── Need copies of existing configured objects? → Prototype

Interview Follow-ups

Q1: How does Prototype differ from simply using new with the same parameters?

A: new requires the client to know the exact class and all constructor parameters. Prototype decouples the client from concrete types - you clone through an interface without knowing the implementation. Additionally, if the object's state was built up over time (computed fields, loaded resources, accumulated state), new cannot replicate that without repeating the entire process. Clone captures the current state directly.

Q2: How would you implement a Prototype Registry that supports versioning?

A: Use a composite key (name, version) or maintain a Map<String, List<Prototype>> where the list is version-ordered. create("config") returns the latest version's clone; create("config", v2) returns a specific version. Each registration is immutable - you add new versions rather than overwriting. This gives you rollback capability and audit trails.

Q3: Can Prototype pattern work with immutable objects?

A: For truly immutable objects, cloning is unnecessary - just share the reference since the object can't change. However, Prototype is useful when you want to create a mutable copy from an immutable template. The clone starts as a copy of the immutable prototype but can then be modified. This is the "copy-on-write" variant.

Q4 (Hint Only): How would you handle deep cloning of an object graph with 10+ levels of nesting and potential cycles?

Hint: Think about serialization-based cloning (serialize to bytes, deserialize to new object) as an alternative to manual recursive copy. Also consider a visitor pattern with a visited set to handle cycles.

Q5 (Hint Only): Design a prototype-based system where clones automatically register themselves back into the registry under a new key.

Hint: Consider a post-clone hook or observer pattern. The clone() method could accept an optional registry reference and key, or the registry's create() method could auto-register the clone with a generated key before returning it.


Counter Questions to Ask Interviewer

  1. "Are the objects being cloned mutable or immutable after creation?" - Determines whether deep copy is necessary or if shared references are safe.

  2. "How deep is the object graph, and are there circular references?" - Influences whether manual deep copy, serialization-based copy, or shallow copy is appropriate.

  3. "Is the prototype set fixed at startup or dynamic at runtime?" - Determines whether a static registry or a dynamic registration mechanism is needed.

  4. "What's the expected clone frequency - occasional or high-throughput?" - Affects whether you optimize for copy speed (object pools, pre-allocated buffers) or simplicity.

  5. "Do cloned objects need unique identity (IDs, timestamps) or should they be exact duplicates?" - Determines post-clone initialization requirements.


References & Whitepapers

  1. Gamma, Helm, Johnson, Vlissides - Design Patterns: Elements of Reusable Object-Oriented Software (1994), Chapter 3: Creational Patterns - Prototype. The canonical reference defining intent, applicability, structure, and consequences.

  2. JavaScript Prototype Chain - ECMAScript specification's prototypal inheritance (Object.create(), __proto__, Object.getPrototypeOf()). JavaScript implements the Prototype pattern at the language level - objects inherit directly from other objects without classes.

  3. Bloch, Joshua - Effective Java, 3rd Edition (2018), Item 13: "Override clone judiciously." Discusses why Java's Cloneable interface is broken by design and recommends copy constructors or copy factory methods as superior alternatives.

  4. Nystrom, Robert - Game Programming Patterns (2014), Chapter "Prototype." Practical application of prototype pattern in game engines for spawning entities from archetypes.

  5. Microsoft .NET Documentation - ICloneable interface guidance and MemberwiseClone() behavior. Discusses the shallow-copy default and when to implement deep copy.

  6. Prototype-Based Programming - Lieberman (1986), "Using Prototypical Objects to Implement Shared Behavior in Object-Oriented Systems." The academic foundation for prototype-based languages (Self, JavaScript, Lua).


  • Singleton Pattern - Contrasts with Prototype; Singleton restricts instances while Prototype multiplies them
  • Factory Method Pattern - Often combined with Prototype via a registry that replaces factory subclasses
  • Abstract Factory Pattern - Can use Prototype internally to create product families from cloned instances
  • Builder Pattern - Builder constructs step-by-step; Prototype copies a finished result. Builder can use a prototype as default starting state
  • Object Pool Pattern - Pools often use Prototype to pre-populate reusable instances

This concludes the Creational Design Patterns section. The Prototype pattern completes the five GoF creational patterns, each addressing a different dimension of object creation: controlling instance count (Singleton), decoupling type selection (Factory Method/Abstract Factory), managing construction complexity (Builder), and optimizing creation cost (Prototype).