Prototype
Clone existing objects without depending on their concrete classes. When creating a fresh object from scratch is expensive or complex, copy an existing one and tweak only what differs.
The Problem
You have an object that took considerable setup to produce — a complex graph loaded from a database, a configured service instance, or a DOM tree. You need a second, slightly different copy. The straightforward approach: create a new object and copy every field. Three problems arise:
- Some fields may be private — you can’t read them from outside the class.
- You must know the object’s concrete class to duplicate it — introducing coupling.
- Sometimes you only have the interface, not the concrete type.
The Solution
Delegate cloning to the object itself. Give each class a clone() method that creates a copy of itself. Callers ask the object to clone — they never need to know the concrete class.
Real-World Analogy
Biological cell division: a cell copies its own DNA and splits into two identical cells. The new cell doesn’t need a blueprint from some external factory — the original cell knows how to replicate itself. You can also think of a photocopier: it doesn’t need to understand what’s on the page to duplicate it.
TypeScript Example
// Prototype interface
interface Prototype<T> {
clone(): T;
}
// A complex, expensive-to-configure object
class DatabaseConnection implements Prototype<DatabaseConnection> {
host: string;
port: number;
database: string;
private connectionPool: string[]; // simulated private state
constructor(host: string, port: number, database: string) {
this.host = host;
this.port = port;
this.database = database;
// Imagine this takes 500ms in real life
this.connectionPool = ['conn-1', 'conn-2', 'conn-3'];
}
clone(): DatabaseConnection {
// Shallow copy via Object.create + spread, then fix deep parts
const copy = Object.create(Object.getPrototypeOf(this));
copy.host = this.host;
copy.port = this.port;
copy.database = this.database;
copy.connectionPool = [...this.connectionPool]; // deep copy the array
return copy;
}
toString(): string {
return `${this.host}:${this.port}/${this.database} [pool: ${this.connectionPool.length}]`;
}
}
// Prototype Registry — an optional convenience layer
class ConnectionRegistry {
private registry = new Map<string, DatabaseConnection>();
register(name: string, connection: DatabaseConnection): void {
this.registry.set(name, connection);
}
get(name: string): DatabaseConnection {
const proto = this.registry.get(name);
if (!proto) throw new Error(`No prototype registered for "${name}"`);
return proto.clone(); // always returns a fresh copy
}
}
// Setup: configure prototypes once
const registry = new ConnectionRegistry();
const primaryDb = new DatabaseConnection('db.primary.local', 5432, 'app_prod');
registry.register('primary', primaryDb);
const analyticsDb = new DatabaseConnection('db.analytics.local', 5432, 'analytics');
registry.register('analytics', analyticsDb);
// Usage: clone without knowing the concrete class or re-running setup
const conn1 = registry.get('primary');
const conn2 = registry.get('primary');
conn2.database = 'app_staging'; // modify the clone, not the prototype
console.log(conn1.toString());
// → db.primary.local:5432/app_prod [pool: 3]
console.log(conn2.toString());
// → db.primary.local:5432/app_staging [pool: 3]
Shallow vs Deep Clone
This is the most important thing to get right in Prototype:
| Shallow Clone | Deep Clone | |
|---|---|---|
| Primitive fields | Copied by value ✓ | Same ✓ |
| Object/array fields | Copies the reference | Copies the contents |
| When to use | Fields are primitives or immutable | Fields contain mutable nested objects |
// Shallow — fast, but nested objects are shared
const shallow = { ...original };
// Deep — safe for nested mutable state
const deep = JSON.parse(JSON.stringify(original)); // simple objects only
// Or use structuredClone() (modern browsers + Node 17+)
const deep2 = structuredClone(original);
When to Use
- Object creation is expensive (database queries, network calls, heavy computation) and cloning is cheaper
- You need copies of objects without coupling to their concrete classes
- You want to avoid a parallel hierarchy of factory classes — let objects clone themselves instead
Pros
- Clone complex objects without coupling to their concrete class
- Avoid repeated expensive initialisation — set up once, clone many times
- An alternative to subclassing for creating object variants
- Prototype registry acts as a built-in object cache
Cons
- Implementing
clone()can be tricky when objects have circular references - Deep cloning complex object graphs requires careful, often bespoke implementation
Related Patterns
- Abstract Factory can use Prototypes to create products by cloning registered instances
- Memento stores object state for undo — Prototype gives you a full snapshot clone
- Decorator wraps objects without copying them — contrast with Prototype’s copy-then-modify approach