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
-
Expensive Object Creation - Objects requiring heavy computation, database queries, network calls, or file I/O during initialization. Cloning bypasses the costly setup entirely.
-
Complex Initialization Logic - Objects with dozens of configuration parameters, nested dependencies, or multi-step setup sequences. A pre-configured prototype eliminates repetitive initialization code.
-
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.
-
Runtime Type Flexibility - When the exact type of object to create is determined at runtime and you want to avoid large
if/elseorswitchblocks tied to concrete classes. -
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 Use | When 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/composed | Class hierarchy is simple and stable |
| Objects differ only in state, not structure | Objects have no meaningful shared initial state |
| You need runtime configuration of object types | Deep copy complexity outweighs creation cost |
| Avoiding parallel class hierarchies (factory per type) | Objects hold non-cloneable resources (live sockets, threads) |
| Undo/snapshot functionality is needed | Immutable objects (just share the reference) |
| Prototype registry can reduce class count | Circular 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
| Aspect | Shallow Copy | Deep Copy |
|---|---|---|
| Mechanism | Copies field values as-is | Recursively copies entire object graph |
| Primitives | Duplicated (independent) | Duplicated (independent) |
| Reference fields | Shared with original | New independent copies |
| Performance | Fast - O(1) per reference field | Slower - O(n) where n = graph size |
| Memory | Lower (shared references) | Higher (full duplication) |
| Independence | Partial - nested mutations propagate | Full - completely independent |
| Complexity | Simple to implement | Must handle cycles, special types |
| Use case | Immutable nested objects, read-only sharing | Mutable 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
| Criteria | Prototype | Factory Method | Builder |
|---|---|---|---|
| Intent | Clone existing instance | Delegate creation to subclasses | Construct complex objects step-by-step |
| When to use | Object creation is expensive; many similar objects needed | Client doesn't know exact class to instantiate | Object has many optional parameters or construction steps |
| Object source | Existing instance (runtime) | Class hierarchy (compile-time) | Accumulated configuration |
| Flexibility | Very high - any configured state | Medium - limited to class variants | High - any combination of parts |
| Complexity | Low (just clone) | Medium (class per product) | Medium-High (director + builder) |
| Avoids | Expensive re-initialization | Direct new coupling | Telescoping constructors |
| Typical participants | Prototype, ConcretePrototype, Client | Creator, ConcreteCreator, Product | Builder, ConcreteBuilder, Director, Product |
| Combines well with | Registry, Composite | Abstract Factory, Template Method | Prototype (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
| Advantages | Disadvantages |
|---|---|
| Avoids costly initialization - clone is typically O(1) to O(n) | Deep copy can be complex for large object graphs |
| Reduces subclass proliferation | Circular references require special handling |
| Hides concrete classes from client | Objects with final/immutable fields can't be modified post-clone |
| Adds/removes products at runtime via registry | Each class must implement clone() - maintenance overhead |
| Preserves object state as a snapshot | Cloning objects with external resources (DB connections) is dangerous |
| Works with Composite/Decorator for complex structures | Language support varies (Java's Cloneable is notoriously broken) |
| Simplifies object creation in dynamic systems | Shallow 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:
- Use copy constructors instead of field-by-field assignment.
- Use reflection (fragile, last resort).
- 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:
| Aspect | Singleton | Factory Method | Abstract Factory | Builder | Prototype |
|---|---|---|---|---|---|
| Intent | Ensure one instance globally | Defer instantiation to subclasses | Create families of related objects | Construct complex objects step-by-step | Clone existing instances |
| Complexity | Low | Medium | High | Medium-High | Low-Medium |
| Primary Use Case | Shared resource (config, logger, pool) | Framework extension points | Cross-platform UI, themed components | Objects with many optional parts | Expensive creation, runtime type selection |
| Number of Classes | 1 | 2+ (creator + products) | 4+ (factories + products) | 3+ (builder + director + product) | 2+ (prototype + concretes) |
| Key Participants | Singleton class | Creator, ConcreteCreator, Product | AbstractFactory, ConcreteFactory, AbstractProduct | Builder, ConcreteBuilder, Director, Product | Prototype, ConcretePrototype, Client/Registry |
| Object Source | Private constructor + static access | Subclass decides | Factory family decides | Step-by-step accumulation | Existing configured instance |
| Flexibility | None (one instance) | Medium (subclass per variant) | High (swap entire families) | Very High (any combination) | Very High (any runtime state) |
| Coupling | Global state coupling | Decouples client from concrete product | Decouples client from product families | Decouples construction from representation | Decouples client from concrete classes |
| When to Avoid | Testing, concurrency issues | Simple creation logic | Only one product family exists | Simple objects with few params | Cheap objects, circular refs |
| Related Patterns | Factory Method (often returns singleton) | Abstract Factory, Template Method, Prototype | Factory Method (implements each method), Singleton | Prototype (default values), Composite | Factory (registry), Composite, Decorator |
| Real-World Analogy | Government (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
-
"Are the objects being cloned mutable or immutable after creation?" - Determines whether deep copy is necessary or if shared references are safe.
-
"How deep is the object graph, and are there circular references?" - Influences whether manual deep copy, serialization-based copy, or shallow copy is appropriate.
-
"Is the prototype set fixed at startup or dynamic at runtime?" - Determines whether a static registry or a dynamic registration mechanism is needed.
-
"What's the expected clone frequency - occasional or high-throughput?" - Affects whether you optimize for copy speed (object pools, pre-allocated buffers) or simplicity.
-
"Do cloned objects need unique identity (IDs, timestamps) or should they be exact duplicates?" - Determines post-clone initialization requirements.
References & Whitepapers
-
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.
-
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. -
Bloch, Joshua - Effective Java, 3rd Edition (2018), Item 13: "Override clone judiciously." Discusses why Java's
Cloneableinterface is broken by design and recommends copy constructors or copy factory methods as superior alternatives. -
Nystrom, Robert - Game Programming Patterns (2014), Chapter "Prototype." Practical application of prototype pattern in game engines for spawning entities from archetypes.
-
Microsoft .NET Documentation -
ICloneableinterface guidance andMemberwiseClone()behavior. Discusses the shallow-copy default and when to implement deep copy. -
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).
Related Topics
- 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).