Design Patterns, Explained Simply · Creational Patterns: Creating Objects Flexibly
Prototype
Create new objects by copying a preconfigured one, and learn why the difference between a shallow and a deep copy decides whether the pattern helps or hurts.
The problem
A game spawns hundreds of enemies. Each "orc archer" starts with the same health, speed and a loadout of items, and setting one up takes a dozen lines (or an expensive load from a data file). Copy-pasting that setup everywhere is error-prone.
So the team keeps one configured orc as a template and copies it. Then a bug report arrives: when one orc picks up a rocket launcher, every orc has one. The copies were sharing the template's item list.
Real-world analogy
A cell divides by copying itself. Each new cell starts as an exact copy, then changes on its own. If two cells shared one nucleus, damage to one would damage both. Cloning only works when each copy gets its own insides.
Without the pattern
def make_orc_archer():
orc = Enemy("orc archer", health=80, speed=3)
orc.loadout = Loadout(["bow", "arrows"])
# ...ten more lines of setup, repeated for every enemy type and every spawn point
return orc
The pattern
- Prototype: an object with a
clone()method that returns a new, independent copy of itself. - Client: keeps one configured prototype per type (often in a registry) and calls
clone()to make new objects, then tweaks the copy. - Shallow vs deep copy: a shallow copy copies the fields, but nested objects (lists, maps, other objects) are shared. A deep copy also copies the nested objects.
clone()must copy every mutable part the copies will change independently.
The code
The first part shows the shallow-copy bug; the second uses a proper clone() that copies the loadout.
class Loadout:
def __init__(self, items):
self.items = items
class Enemy:
def __init__(self, kind, health, loadout):
self.kind, self.health, self.loadout = kind, health, loadout
def shallow_clone(self): # copies fields only: the loadout object is shared
return Enemy(self.kind, self.health, self.loadout)
def clone(self): # copies the nested loadout too
return Enemy(self.kind, self.health, Loadout(list(self.loadout.items)))
template = Enemy("orc archer", 80, Loadout(["bow", "arrows"]))
a = template.shallow_clone()
a.loadout.items.append("rocket launcher")
print("shallow: template has", template.loadout.items)
template = Enemy("orc archer", 80, Loadout(["bow", "arrows"]))
b = template.clone()
b.loadout.items.append("rocket launcher")
b.health = 40
print("deep: template has", template.loadout.items, "health", template.health)
print("deep: copy has", b.loadout.items, "health", b.health)
Output
shallow: template has ['bow', 'arrows', 'rocket launcher'] deep: template has ['bow', 'arrows'] health 80 deep: copy has ['bow', 'arrows', 'rocket launcher'] health 40
class Loadout {
constructor(public items: string[]) {}
}
class Enemy {
constructor(public kind: string, public health: number, public loadout: Loadout) {}
shallowClone(): Enemy { // copies fields only: the loadout object is shared
return new Enemy(this.kind, this.health, this.loadout);
}
clone(): Enemy { // copies the nested loadout too
return new Enemy(this.kind, this.health, new Loadout([...this.loadout.items]));
}
}
const show = (items: string[]) => `[${items.map((i) => `'${i}'`).join(", ")}]`;
let template = new Enemy("orc archer", 80, new Loadout(["bow", "arrows"]));
const a = template.shallowClone();
a.loadout.items.push("rocket launcher");
console.log("shallow: template has", show(template.loadout.items));
template = new Enemy("orc archer", 80, new Loadout(["bow", "arrows"]));
const b = template.clone();
b.loadout.items.push("rocket launcher");
b.health = 40;
console.log("deep: template has", show(template.loadout.items), "health", template.health);
console.log("deep: copy has", show(b.loadout.items), "health", b.health);
Output
shallow: template has ['bow', 'arrows', 'rocket launcher'] deep: template has ['bow', 'arrows'] health 80 deep: copy has ['bow', 'arrows', 'rocket launcher'] health 40
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
static String show(List<String> items) {
return items.stream().map(i -> "'" + i + "'").collect(Collectors.joining(", ", "[", "]"));
}
public static void main(String[] args) {
Enemy template = new Enemy("orc archer", 80, new Loadout(new ArrayList<>(List.of("bow", "arrows"))));
Enemy a = template.shallowClone();
a.loadout.items.add("rocket launcher");
System.out.println("shallow: template has " + show(template.loadout.items));
template = new Enemy("orc archer", 80, new Loadout(new ArrayList<>(List.of("bow", "arrows"))));
Enemy b = template.copy();
b.loadout.items.add("rocket launcher");
b.health = 40;
System.out.println("deep: template has " + show(template.loadout.items) + " health " + template.health);
System.out.println("deep: copy has " + show(b.loadout.items) + " health " + b.health);
}
}
class Loadout {
final List<String> items;
Loadout(List<String> items) { this.items = items; }
}
class Enemy {
String kind;
int health;
Loadout loadout;
Enemy(String kind, int health, Loadout loadout) { this.kind = kind; this.health = health; this.loadout = loadout; }
Enemy shallowClone() { return new Enemy(kind, health, loadout); } // the loadout object is shared
Enemy copy() { return new Enemy(kind, health, new Loadout(new ArrayList<>(loadout.items))); } // copies it too
}
Output
shallow: template has ['bow', 'arrows', 'rocket launcher'] deep: template has ['bow', 'arrows'] health 80 deep: copy has ['bow', 'arrows', 'rocket launcher'] health 40
Run this on your computer — see “Get Started” for setup.
<?php
class Loadout {
public function __construct(public array $items) {}
}
class Enemy {
public function __construct(public string $kind, public int $health, public Loadout $loadout) {}
// PHP's `clone` is shallow: object properties are shared unless __clone copies them
public function __clone() {
$this->loadout = clone $this->loadout;
}
}
function show(array $items): string {
return "[" . implode(", ", array_map(fn($i) => "'$i'", $items)) . "]";
}
$template = new Enemy("orc archer", 80, new Loadout(["bow", "arrows"]));
$a = new Enemy($template->kind, $template->health, $template->loadout); // a hand-made shallow copy
$a->loadout->items[] = "rocket launcher";
echo "shallow: template has ", show($template->loadout->items), "\n";
$template = new Enemy("orc archer", 80, new Loadout(["bow", "arrows"]));
$b = clone $template; // __clone copies the nested loadout
$b->loadout->items[] = "rocket launcher";
$b->health = 40;
echo "deep: template has ", show($template->loadout->items), " health ", $template->health, "\n";
echo "deep: copy has ", show($b->loadout->items), " health ", $b->health, "\n";
Output
shallow: template has ['bow', 'arrows', 'rocket launcher'] deep: template has ['bow', 'arrows'] health 80 deep: copy has ['bow', 'arrows', 'rocket launcher'] health 40
The shallow copy leaked the rocket launcher back into the template. The proper clone() gave the copy its own loadout, so changes stay local.
When to use it
- Creating an object from scratch is expensive or complicated (loaded from files, many setup steps), and you need many similar ones.
- You want to create objects from configured examples chosen at runtime (a palette of shapes in a drawing app, enemy templates in a game).
- You want to copy an object without knowing its concrete class (
shape.clone()works for any shape).
When not to use it
- Objects are cheap and simple to construct. A constructor or factory is clearer.
- The object graph has cycles or external resources (open files, sockets, database connections). Deep-copying those correctly is hard, and often wrong.
- Immutable objects never need cloning: share them freely, and "modify" by creating a new one with changes.
You've already used it
- JavaScript: prototypal inheritance itself.
Object.create(proto)makes objects from an existing one, andstructuredClone()makes deep copies of plain data. - Python:
copy.copy()(shallow) andcopy.deepcopy()(deep), customisable with__copy__and__deepcopy__. - PHP: the
clonekeyword and__clone(). Java:Cloneableand copy constructors (copy constructors are usually preferred).
Key points
- Prototype creates objects by copying a configured instance with
clone(). - The critical detail is shallow vs deep copying: nested mutable objects must be copied, or clones share them.
- Immutable objects make Prototype unnecessary and avoid the sharing bug entirely.
- Further reading: Refactoring Guru: Prototype and Python's copy module.
Practice questions
What is the difference between a shallow copy and a deep copy, and why does it matter for Prototype?
Answer
A shallow copy copies an object's fields, but nested objects (lists, maps, other objects) are shared between the original and the copy. A deep copy also copies the nested objects. With a shallow clone, changing a nested list on the copy changes the prototype too, the classic Prototype bug.
When is the Prototype pattern useful?
Answer
When objects are expensive or complicated to create from scratch and you need many similar ones, or when you want to create objects from configured examples chosen at runtime, such as enemy templates or shapes in a palette.
How does PHP's `clone` keyword behave with object properties?
Answer
It makes a shallow copy: scalar and array properties are copied, but object properties still point to the same objects. Implement __clone() to clone nested objects that the copy must own.
Open in the app to mark lessons complete, take notes and track your progress.