On this page
Description
Prototype is the creational pattern for "set one up, then copy it": when building an object is expensive - loading a model, parsing a config, precomputing data - you build a ready-made template once and create new objects by copying it. The pattern is simple; the craft is in the copy, because every field needs a decision about whether the copy gets its own or shares the original's. This skill covers that decision, prototype registries, and why Java's clone() is best avoided.
Setting up some objects is expensive: a game enemy type needs its model loaded from disk and its pathfinding data precomputed; a report template needs a query parsed and a layout calculated. If every new instance repeats that work, spawning a hundred enemies costs a hundred loads - even though they start out identical and differ only in a name or a position.
The alternative is to set one up once and copy it. The hard part is making every copy truly independent of the original.
We'll go from simple to more technical:
- The pattern: a
copy()method that decides, field by field, what to share. - Shallow versus deep copies, in Java and Python.
- A registry of prototypes, and why it copies on the way in and out.
- When Prototype is the wrong tool, and choosing among the creational patterns.
The pattern
Java
class GameCharacter {
private final Model model; // expensive to load, immutable - safe to share
private final List<String> items; // mutable - every copy needs its own
String name;
GameCharacter(Model model) { this.model = model; this.items = new ArrayList<>(); }
private GameCharacter(GameCharacter other) { // copy constructor
this.model = other.model; // shared on purpose
this.items = new ArrayList<>(other.items); // copied
this.name = other.name;
}
GameCharacter copy() { return new GameCharacter(this); }
}
GameCharacter template = new GameCharacter(loadModel()); // expensive, done once
GameCharacter orcA = template.copy(); orcA.name = "Orc A";
GameCharacter orcB = template.copy(); orcB.name = "Orc B";
Python
import copy
class GameCharacter:
def __init__(self, model: Model):
self.model = model # expensive to load, immutable - safe to share
self.items: list[str] = [] # mutable - every copy needs its own
self.name = ""
def copy(self) -> "GameCharacter":
c = copy.copy(self) # a shallow copy: every field shared...
c.items = list(self.items) # ...so give the mutable one its own list
return c
template = GameCharacter(load_model()) # expensive, done once
orc_a = template.copy(); orc_a.name = "Orc A"
orc_b = template.copy(); orc_b.name = "Orc B"
Every field gets a deliberate decision: the immutable model is shared, the mutable list is copied. The common mistakes below cover what goes wrong when that decision is skipped; the deep dive explains why this uses a copy() method rather than Java's clone(). In Python, copy.copy(obj) is the shallow copy and copy.deepcopy(obj) copies everything reachable - including the expensive model you meant to share; a class can steer both with __copy__ and __deepcopy__.
A registry of prototypes
With many templates, keep them in a map - and copy on the way in and on the way out:
Java
class PrototypeRegistry {
private final Map<String, GameCharacter> templates = new HashMap<>();
void register(String key, GameCharacter t) { templates.put(key, t.copy()); } // keep our own copy
GameCharacter spawn(String key) {
GameCharacter t = templates.get(key);
return t == null ? null : t.copy(); // never hand out the template
}
}
Python
class PrototypeRegistry:
def __init__(self):
self._templates: dict[str, GameCharacter] = {}
def register(self, key: str, t: GameCharacter) -> None:
self._templates[key] = t.copy() # keep our own copy
def spawn(self, key: str) -> GameCharacter | None:
t = self._templates.get(key)
return None if t is None else t.copy() # never hand out the template
Copying on the way in stops the caller from changing a template later through the reference it still holds. Copying on the way out stops a spawned object from changing the template.
Where you'll see it
Cloning a pre-configured "default" object (a request template, a default game-entity spawn config) is common wherever most instances of something are 90% identical and only a couple of fields vary per instance.
Choosing among the creational patterns
| If you need... | Reach for | Because |
|---|---|---|
| Exactly one shared instance, by design | Singleton | Two copies would be a bug (e.g. two connection pools fighting over the same database connections). |
| One place that decides which concrete class to build | Factory Method | Callers depend on an interface; adding a type changes the factory, not every caller. |
| A matched family of related objects that must vary together | Abstract Factory | Swapping the factory swaps the whole family, so mismatched pieces can't be combined. |
| An object with many optional parts, assembled step by step | Builder | Named, optional steps instead of long positional constructors; validate once in build(). |
| Many near-identical objects that are expensive to set up | Prototype (this lesson) | Copy a ready-made template instead of repeating costly construction. |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| A shallow copy of an object with mutable fields | changes to one copy appear in the others | copy each mutable field explicitly |
| A deep copy of everything | the expensive part you meant to share is copied too | decide per field: share immutables, copy mutables |
Using Java's clone() / Cloneable | no constructor runs; final fields can't get fresh copies | a copy constructor or copy() method |
| Handing out the registry's template itself | a spawned object changes the template | copy on the way in and on the way out |
| Prototype for objects that are cheap to build | copying decisions with nothing saved | just call the constructor |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. A Document has a String title and a List<String> tags. Which field must its copy method copy, and which can it share?
Show answer
Copy tags - it is mutable, and sharing it links the two documents. Share title - a String is immutable.
2. Why should a prototype registry store a copy of the template it is given, not the caller's object?
Show answer
The caller still holds a reference. If it changes that object later, every future spawn would change with it.
3. Creating the object is cheap - a couple of field assignments, no I/O. Prototype?
Show answer
No. Prototype pays off when setup is expensive; for a cheap constructor, copying adds decisions (deep or shallow?) and saves nothing.
Key takeaways
- Copy a pre-configured template when building from scratch is expensive or complicated.
- A shallow copy shares mutable fields (lists, maps) with the original - changing one silently changes the other.
- Deep-copy every mutable field; immutable fields such as
Stringcan safely be shared. - A copy constructor or a
copy()method is usually clearer and safer thanObject.clone()- in Python, acopy()method that decides field by field, sincecopy.deepcopyalso copies what should stay shared.