Back to Design Patterns
Creational Pattern

Prototype Pattern

Create new objects by copying an existing object (the prototype) rather than constructing them from scratch — preserving state and avoiding expensive initialisation.

Intermediate10 min read

What is the Prototype Pattern?

The Prototype pattern creates new objects by cloning an existing object — the prototype — instead of going through a full construction process. The clone starts as an identical copy, then you modify only what needs to change.

Think of a cell mitosis: rather than building a new cell from amino acids up, a cell copies itself. The copy inherits everything and then differentiates. Or a document template — you clone an invoice template and fill in the client's name, rather than laying out every font and margin again.

This is especially valuable when object creation is expensive (database reads, API calls, complex initialisation) and you need many similar objects that differ only slightly.

Key Idea: Clone a fully initialised object instead of constructing a new one. Modify only what differs. Avoid the cost of repeated construction.

Shallow Copy vs Deep Copy

This is the most critical concept to understand when implementing Prototype correctly:

Shallow Copy

Copies primitive fields by value. Copies object/array fields by reference — the clone and original share the same nested objects.

const a = { x: 1, tags: ['a'] };
const b = Object.assign({}, a); // shallow
b.tags.push('b');
console.log(a.tags); // ['a', 'b'] — shared!

✗ Mutating nested objects affects the original

Deep Copy

Recursively copies everything — primitives and nested objects alike. Clone and original are completely independent.

const a = { x: 1, tags: ['a'] };
const b = structuredClone(a); // deep
b.tags.push('b');
console.log(a.tags); // ['a'] — unaffected

✓ Clone is fully independent from original

Rule: Almost always use deep copy in Prototype implementations. Shallow copies cause subtle shared-state bugs that are extremely hard to track down.

Core Participants

Prototype Interface

Prototype Interface

Declares the clone() method. Any object that can be cloned implements this. It's the contract that allows the client to clone without knowing the concrete class.

Concrete Prototype

Concrete Prototype

Implements clone() — performs the actual copying of its own data (shallow or deep). Each concrete prototype is responsible for making a correct independent copy of itself.

Prototype Registry

Prototype Registry (optional)

A store of pre-built prototype objects indexed by key. Clients ask the registry to clone a named prototype rather than maintaining references themselves.

Client

Client

Requests a clone from a prototype or registry. It gets a new independent object without needing to know the concrete class or how it was built.

!Problem It Solves

Expensive Object Construction

Some objects require heavy setup — loading config from a database, making API calls, running complex calculations. If you need 100 similar objects, constructing each one from scratch is wasteful.

// Without Prototype — expensive construction repeated
for (int i = 0; i < 100; i++) {
    // 3 DB calls + 2 API calls per character — 500 total calls!
    GameCharacter npc = new GameCharacter("NPC-" + i);
}

// With Prototype — construct once, clone 99 times
GameCharacter template = new GameCharacter("template"); // 5 calls once
for (int i = 0; i < 100; i++) {
    GameCharacter npc = template.clone(); // free!
    npc.setName("NPC-" + i);
}

Implementation

// Prototype Pattern — Game Character Cloning

// ─── Prototype interface ──────────────────────────────────────
interface Prototype<T> {
    T clone();
}

// ─── Concrete Prototype ───────────────────────────────────────
class GameCharacter implements Prototype<GameCharacter> {
    private String name;
    private String characterClass;
    private int    level;
    private int    health;
    private int    attack;
    private java.util.List<String> skills;
    private java.util.Map<String, Integer> stats;

    public GameCharacter(String name, String characterClass, int level,
                         int health, int attack,
                         java.util.List<String> skills,
                         java.util.Map<String, Integer> stats) {
        this.name           = name;
        this.characterClass = characterClass;
        this.level          = level;
        this.health         = health;
        this.attack         = attack;
        // Deep copy collections so clone is independent
        this.skills = new java.util.ArrayList<>(skills);
        this.stats  = new java.util.HashMap<>(stats);
    }

    @Override
    public GameCharacter clone() {
        // Deep clone — changes to clone don't affect original
        return new GameCharacter(name, characterClass, level,
                health, attack,
                new java.util.ArrayList<>(skills),
                new java.util.HashMap<>(stats));
    }

    public void setName(String name)     { this.name = name; }
    public void levelUp()                { this.level++; this.health += 50; this.attack += 5; }
    public void addSkill(String skill)   { this.skills.add(skill); }

    @Override
    public String toString() {
        return String.format("GameCharacter{name='%s', class='%s', level=%d, hp=%d, atk=%d, skills=%s}",
                name, characterClass, level, health, attack, skills);
    }
}

// ─── Prototype Registry ───────────────────────────────────────
class CharacterRegistry {
    private java.util.Map<String, GameCharacter> templates = new java.util.HashMap<>();

    public void registerTemplate(String key, GameCharacter character) {
        templates.put(key, character);
        System.out.println("[Registry] Registered template: " + key);
    }

    public GameCharacter spawn(String key) {
        GameCharacter template = templates.get(key);
        if (template == null) throw new RuntimeException("No template: " + key);
        GameCharacter clone = template.clone();
        System.out.println("[Registry] Spawned from template: " + key);
        return clone;
    }
}

// ─── Client ───────────────────────────────────────────────────
public class App {
    public static void main(String[] args) {
        // Create expensive base templates once
        GameCharacter warriorTemplate = new GameCharacter(
            "Warrior", "Fighter", 1, 200, 30,
            java.util.Arrays.asList("Slash", "Block"),
            java.util.Map.of("strength", 15, "defense", 12)
        );

        CharacterRegistry registry = new CharacterRegistry();
        registry.registerTemplate("warrior", warriorTemplate);

        // Clone is far cheaper than re-constructing
        GameCharacter player1 = registry.spawn("warrior");
        player1.setName("ArjunX99");
        player1.levelUp();
        player1.addSkill("Power Strike");

        GameCharacter player2 = registry.spawn("warrior");
        player2.setName("ViratGaming");

        System.out.println("Player1: " + player1);
        System.out.println("Player2: " + player2);
        // player1 levelling up did NOT affect player2 — deep copy!
    }
}

When to Use Prototype

Expensive Object Initialisation

When construction requires DB queries, API calls, or complex calculations — build the prototype once and clone for each new instance.

GameCharacter, ConfiguredService, ParsedDocument

Many Similar Objects

When you need large numbers of objects that are mostly identical with small variations — clone a template and tweak what differs.

BulkEmailTemplates, NPCSpawner, ReportCloner

Class Decoupling at Runtime

When you want to create objects without knowing their concrete class — just clone whatever prototype the registry returns.

PluginSystem, DynamicObjectFactory

Undo / History States

When you need to snapshot an object's current state for undo functionality — clone it, then store the clone as the undo point.

EditorSnapshot, GameSaveState, FormDraft

Pros & Cons

Advantages

  • Avoids repeated expensive construction
  • Clone without coupling to concrete classes
  • Easily add/remove prototypes at runtime
  • Combine with Prototype Registry for clean object management
  • Preserves complex pre-built state without reconstruction

Disadvantages

  • Deep cloning complex circular references is tricky
  • Each class must implement its own clone() correctly
  • Easy to accidentally use shallow copy and create bugs
  • Cloning objects with private fields can be difficult

Real-World Examples

Java's Object.clone() and Cloneable

Java has built-in prototype support. Classes implement Cloneable and override clone() from Object. Spring's BeanDefinition uses prototype scope — each getBean() call returns a fresh clone.

Python's copy module

Python's copy.copy() (shallow) and copy.deepcopy() (deep) are direct Prototype implementations. Django model instances can be cloned by setting pk=None and calling save().

JavaScript Spread Operator / structuredClone

Spread syntax ({...obj}) is shallow cloning. structuredClone() is the modern deep clone API. React's immutable state updates (spreading and modifying) follow the prototype concept.

Redux State Snapshots

Time-travel debugging in Redux works by deep cloning the store state at each action — these state snapshots are prototypes used to restore previous application states.

What's Next?

Explore related Creational patterns: