The Prototype pattern creates new objects by cloning an existing object instead of constructing one from scratch. We take a fully configured object and make a copy of it.
Think of it like photocopying a document. Instead of typing the whole thing again, we just photocopy it and change what we need. Way faster, way less error-prone.
The Problem It Solves
Sometimes creating an object is expensive. Maybe it requires:
- A database query to load initial data
- Complex calculations to set up state
- Reading from a file or network
If we need many similar objects, building each one from scratch is wasteful. Instead, we create one, clone it, and tweak the differences.
Another scenario: we want a copy of an object but don’t know its concrete class. The object might be behind an interface. We can’t call new WhateverItIs() because we don’t know the type. But we can ask the object to clone itself.
How It Works
hp: 100
weapon: Sword
hp: 100
weapon: Sword (copy)
Shallow Copy vs Deep Copy
This is the most important thing to understand about Prototype. Interviewers love asking this.
- Shallow copy: copies the object, but nested objects are still shared references. Changing a nested object in the clone also changes it in the original.
- Deep copy: copies everything, including nested objects. The clone is 100% independent.
import copy
class Weapon:
def __init__(self, name: str, damage: int):
self.name = name
self.damage = damage
class GameCharacter:
def __init__(self, name: str, hp: int, weapon: Weapon):
self.name = name
self.hp = hp
self.weapon = weapon
def clone(self):
# Deep copy -- weapon object is also duplicated
return copy.deepcopy(self)
def __str__(self):
return f"{self.name} (HP:{self.hp}, Weapon:{self.weapon.name})"
# Create original character
sword = Weapon("Excalibur", 50)
warrior = GameCharacter("Warrior", 100, sword)
# Clone and customize
warrior2 = warrior.clone()
warrior2.name = "Warrior 2"
warrior2.weapon.name = "Dark Sword" # only changes the clone
print(warrior) # Warrior (HP:100, Weapon:Excalibur)
print(warrior2) # Warrior 2 (HP:100, Weapon:Dark Sword)
# Shallow copy danger:
import copy
shallow = copy.copy(warrior)
shallow.weapon.name = "BROKEN"
print(warrior.weapon.name) # BROKEN! -- shared reference
class Weapon {
constructor(name, damage) {
this.name = name;
this.damage = damage;
}
}
class GameCharacter {
constructor(name, hp, weapon) {
this.name = name;
this.hp = hp;
this.weapon = weapon;
}
// Deep clone using structuredClone (modern JS)
clone() {
return Object.assign(
Object.create(Object.getPrototypeOf(this)),
structuredClone(this) // deep copy all properties
);
}
toString() {
return `${this.name} (HP:${this.hp}, Weapon:${this.weapon.name})`;
}
}
const sword = new Weapon("Excalibur", 50);
const warrior = new GameCharacter("Warrior", 100, sword);
const warrior2 = warrior.clone();
warrior2.name = "Warrior 2";
warrior2.weapon.name = "Dark Sword"; // only changes the clone
console.log(warrior.toString()); // Warrior (HP:100, Weapon:Excalibur)
console.log(warrior2.toString()); // Warrior 2 (HP:100, Weapon:Dark Sword)
// Shallow copy danger:
// const shallow = { ...warrior };
// shallow.weapon.name = "BROKEN";
// console.log(warrior.weapon.name); // BROKEN! shared reference
public class Weapon implements Cloneable {
String name;
int damage;
Weapon(String name, int damage) {
this.name = name;
this.damage = damage;
}
@Override
public Weapon clone() {
return new Weapon(this.name, this.damage);
}
}
public class GameCharacter implements Cloneable {
String name;
int hp;
Weapon weapon;
GameCharacter(String name, int hp, Weapon weapon) {
this.name = name;
this.hp = hp;
this.weapon = weapon;
}
// Deep clone -- also clone the weapon
@Override
public GameCharacter clone() {
return new GameCharacter(this.name, this.hp, this.weapon.clone());
}
}
// Usage:
// Weapon sword = new Weapon("Excalibur", 50);
// GameCharacter warrior = new GameCharacter("Warrior", 100, sword);
// GameCharacter warrior2 = warrior.clone();
// warrior2.name = "Warrior 2";
// warrior2.weapon.name = "Dark Sword"; // only changes the clone
Prototype Registry
Sometimes we keep a registry of pre-configured prototypes. Need a new wizard? Clone the wizard template. Need a tank? Clone the tank template.
class CharacterRegistry:
def __init__(self):
self._prototypes = {}
def register(self, key: str, prototype: GameCharacter):
self._prototypes[key] = prototype
def create(self, key: str) -> GameCharacter:
if key not in self._prototypes:
raise ValueError(f"Unknown character type: {key}")
return self._prototypes[key].clone()
# Set up templates once
registry = CharacterRegistry()
registry.register("warrior", GameCharacter("Warrior", 100, Weapon("Sword", 50)))
registry.register("mage", GameCharacter("Mage", 60, Weapon("Staff", 80)))
# Create new characters by cloning templates
player1 = registry.create("warrior")
player1.name = "Arthas"
player2 = registry.create("mage")
player2.name = "Gandalf"
class CharacterRegistry {
#prototypes = new Map();
register(key, prototype) {
this.#prototypes.set(key, prototype);
}
create(key) {
const proto = this.#prototypes.get(key);
if (!proto) throw new Error(`Unknown type: ${key}`);
return proto.clone();
}
}
const registry = new CharacterRegistry();
registry.register("warrior", new GameCharacter("Warrior", 100, new Weapon("Sword", 50)));
registry.register("mage", new GameCharacter("Mage", 60, new Weapon("Staff", 80)));
const player1 = registry.create("warrior");
player1.name = "Arthas";
public class CharacterRegistry {
private Map<String, GameCharacter> prototypes = new HashMap<>();
public void register(String key, GameCharacter prototype) {
prototypes.put(key, prototype);
}
public GameCharacter create(String key) {
GameCharacter proto = prototypes.get(key);
if (proto == null) throw new IllegalArgumentException("Unknown: " + key);
return proto.clone();
}
}
When to Use
- Creating an object is expensive (DB calls, file reads, complex setup)
- We need many similar objects with small variations
- We want to copy an object without depending on its concrete class
- Game development — spawning enemies, creating item variants
When NOT to Use
- When objects are simple and cheap to create — cloning adds complexity for no gain
- When objects have circular references — deep copy can get tricky
- When the class has very few fields — just use a constructor
In simple language, Prototype says “don’t build from scratch, photocopy and edit.” It’s perfect when construction is expensive or when we need many objects that are mostly the same. Just remember: always deep copy if the object has nested objects, or we’ll end up with shared state bugs that are a nightmare to debug.