Prototype Pattern
Create new objects by copying an existing object, avoiding the cost of creating from scratch.
The Problem
Imagine you're building a game level editor. A player character object takes several seconds to initialize: it loads mesh data from disk, parses a configuration file, runs physics pre-computation, and resolves dozens of asset references. You need fifty slightly-different variants of that character for a crowd scene.
Calling new Character() fifty times repeats all that expensive setup work. Worse, some of the data live in private fields — you can't copy them from outside the class without breaking encapsulation. And if you want to copy generically (without knowing the concrete type at call-site), you can't call a constructor you don't know the name of.
The core tension: you need copies, but you can't copy from the outside, and you don't want to pay full initialization cost every time.
The Solution
The Prototype pattern solves this by delegating the copy responsibility to the object itself. Every cloneable class implements a clone() method that knows exactly how to duplicate its own state — including private fields — and returns a new instance pre-loaded with that state.
The caller never needs to know the concrete class. It holds a reference to a Prototype interface, calls clone(), and gets back a ready-to-use copy. A PrototypeRegistry can extend this idea by acting as a catalogue of pre-configured prototypes: ask for a "heavy-infantry" character and get a fully initialized clone without touching the filesystem.
Structure
Loading diagram...
The Prototype interface declares one method: clone(). Each concrete class implements it, returning a copy of itself. The PrototypeRegistry stores pre-built prototypes by key; clients ask the registry for a named prototype instead of constructing one directly.
Shallow vs Deep Copy
Shallow copy vs deep copy — this matters
A shallow copy duplicates the object's own fields but copies references to nested objects, not the nested objects themselves. Two clones will share the same nested object, so mutating it in one clone affects the other.
A deep copy recursively duplicates every nested object. The clones are fully independent. Use deep copy whenever clones will be modified independently — which is almost always the case for Prototype.
Consider a Shape with a style object containing color and strokeWidth. A shallow clone points to the same style instance. Change clone.style.color and you've also changed the original's color. A deep clone copies the style object too, so both are independent.
Always decide consciously which strategy you need. In practice, most Prototype implementations use deep copy.
Implementation
The example below models a shape editor. Shape is the prototype base. Circle and Rectangle are concrete prototypes. A ShapeRegistry stores pre-configured shapes that clients clone on demand.
// Prototype interface
interface Shape {
clone(): Shape;
draw(): string;
}
// Shared style — will be deep-copied
interface Style {
color: string;
strokeWidth: number;
}
// Concrete Prototype: Circle
class Circle implements Shape {
constructor(
public radius: number,
public style: Style
) {}
clone(): Shape {
// Deep copy: create a new Style object
return new Circle(this.radius, { ...this.style });
}
draw(): string {
return `Circle(r=${this.radius}, color=${this.style.color})`;
}
}
// Concrete Prototype: Rectangle
class Rectangle implements Shape {
constructor(
public width: number,
public height: number,
public style: Style
) {}
clone(): Shape {
return new Rectangle(this.width, this.height, { ...this.style });
}
draw(): string {
return `Rectangle(${this.width}x${this.height}, color=${this.style.color})`;
}
}
// Prototype Registry
class ShapeRegistry {
private items: Map<string, Shape> = new Map();
add(key: string, shape: Shape): void {
this.items.set(key, shape);
}
get(key: string): Shape {
const proto = this.items.get(key);
if (!proto) throw new Error(`No prototype registered for key: ${key}`);
return proto.clone();
}
}
// --- Usage ---
const registry = new ShapeRegistry();
registry.add("small-red-circle", new Circle(5, { color: "red", strokeWidth: 1 }));
registry.add("large-blue-rect", new Rectangle(100, 50, { color: "blue", strokeWidth: 2 }));
const c1 = registry.get("small-red-circle") as Circle;
const c2 = registry.get("small-red-circle") as Circle;
c1.style.color = "green"; // mutate clone 1
console.log(c1.draw()); // Circle(r=5, color=green)
console.log(c2.draw()); // Circle(r=5, color=red) — unaffected
console.log(registry.get("large-blue-rect").draw()); // Rectangle(100x50, color=blue)Real-World Use Cases
Game development — NPC spawning. A game engine keeps a palette of pre-configured enemy archetypes (sprite loaded, AI behavior tree compiled, stats set). When a level spawns fifty goblins, it clones the "goblin" prototype. Each clone gets its own mutable state (position, health) while sharing the expensive read-only data that was already set up.
Document templates. Word processors and presentation tools maintain template objects. When a user creates a "new document from template," the application clones the template prototype. The clone starts fully formatted; the user never waits for template re-parsing.
Undo/redo systems. Before an operation mutates an object, the editor saves a clone of the current state as a snapshot. The undo stack is a list of these snapshots. Restoring a previous state means replacing the current object with a saved clone — no need for a separate Memento object if your clone is cheap enough.
JavaScript's prototype chain. JavaScript is natively prototype-based. Object.create(proto) creates a new object whose internal prototype points to proto, delegating property lookups up the chain. This is a structural form of prototypal inheritance, conceptually related to the Prototype pattern even though it differs in mechanism.
Trade-offs
Pros
- Cloning can be significantly cheaper than running a full constructor, especially when initialization involves I/O or heavy computation.
- You can produce copies without coupling to the concrete class — the caller only needs the
Prototypeinterface. - Pre-configured prototypes in a registry give you a flexible alternative to hard-coded factory logic.
- Private fields are accessible inside
clone(), so you can copy state that would be invisible to external code.
Cons
- Deep copying complex object graphs (circular references, shared sub-objects) is non-trivial and error-prone. Forgetting to deep-copy a nested field is a subtle bug.
- Circular references require explicit handling — naive recursive deep copy will overflow the stack.
- Each class must implement and maintain
clone(). As classes evolve and gain new fields,clone()must be updated in sync. - The pattern can obscure where objects originate, making debugging harder compared to an explicit constructor call.
When to Use
Use Prototype when:
- Object initialization is expensive (I/O, computation) and you need many similar objects.
- You need to copy objects without depending on their concrete classes at the call-site.
- You want a registry of pre-configured objects that clients can clone and customize.
- The number of classes would explode if you used subclassing to capture every configuration variant.
Avoid Prototype when:
- Objects are cheap to construct and have no private state worth copying.
- Your language already provides value semantics with copy-on-write (e.g., most
structtypes in Go or Rust are copied by default). - The object graph is deeply nested with circular references and no deep-copy utility exists in your stack.
Related Patterns
Abstract Factory can use Prototype internally: instead of hard-coding which class to instantiate, a factory stores prototypes and clones them. This lets you swap product families by swapping the prototype set.
Composite and Decorator objects often need to be cloned as part of a larger tree. Prototype's clone() applied recursively down a Composite tree produces a full independent copy of a structure.
Memento also captures and restores object state, but from the outside. When the cloning cost is low, a clone serves as a simpler alternative to Memento for implementing undo. When objects are large or snapshots are partial, Memento's targeted state extraction is more appropriate.