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:

  1. Some fields may be private — you can’t read them from outside the class.
  2. You must know the object’s concrete class to duplicate it — introducing coupling.
  3. 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.

«interface» Prototype + clone(): Prototype ConcretePrototype1 - field1: string + clone(): this ConcretePrototype2 - field2: number + clone(): this clone()
Prototype — each class clones itself; the self-loop shows clone() returning a new instance

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 CloneDeep Clone
Primitive fieldsCopied by value ✓Same ✓
Object/array fieldsCopies the referenceCopies the contents
When to useFields are primitives or immutableFields 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

  • 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