Before you start
Prototype is the least flashy creational pattern, but it has the clearest use case: slow to build + a small set of templates + independent copies.
If any of those three is missing, don't use Prototype.
The signal to use it
Look at the code that builds a new object. Ask:
- Does this take real time / memory / disk I/O?
- Am I building nearly the same thing over and over, changing only 10-20% of the fields?
- Do the resulting objects need to be independent (changing one shouldn't touch another)?
Three yeses → Prototype. Otherwise → just build fresh.
Where Prototype shows up in design problems
Real examples that come up:
- Game character templates. (This lesson.)
- Config templates. A base "dev" config that gets copied and tweaked per environment.
- HTTP request templates. A pre-configured client with auth headers + timeout policy; each real request copies it and adds path + body.
- Report templates. Pre-loaded chart definitions copied and filled with per-report data.
- UI dashboard widgets. Pre-built widgets copied and dropped into a user's dashboard.
Prototype often comes with a Registry (like our Design D). The registry becomes the "list of templates" the app knows about.
The deepcopy subtlety
copy.deepcopy is Python's built-in for making an
independent copy of an object. It handles most cases,
but has trouble with:
- File handles / sockets / DB connections — copying
them doesn't copy the underlying resource. Usually a
bug. Fix: write a
__deepcopy__method that skips or re-opens them. - Objects with cycles — deepcopy handles cycles correctly (it tracks what it's seen), so mostly fine. But performance can suffer on huge graphs.
__slots__classes — deepcopy usually works, but edge cases fail quietly.- Objects with
__reduce__or unusual pickle behavior — surprises can happen.
For "plain data" objects (dicts, lists, primitives, nested objects of the same kind), deepcopy Just Works. That's why Prototype is most common in games and configuration systems — that's where the objects tend to be plain data.
The escape hatch: __deepcopy__
For objects with expensive-to-copy or uncopyable parts,
write a __deepcopy__(self, memo) method that controls
the copy:
class Character:
def __init__(self, name, ...):
self.name = name
# a big cached lookup we don't want to duplicate
self._skill_tree_cache = _load_from_disk(name)
def __deepcopy__(self, memo):
new = Character(self.name, ...)
# share the (never-changed) cache instead of copying it
new._skill_tree_cache = self._skill_tree_cache
return new
This is the way out when "expensive to build AND some parts shouldn't be copied". Most design problems don't need it, but knowing the escape hatch exists gets you out of jams.
Prototype vs Factory
Both build objects. Difference:
- Factory — builds from CLASSES.
VehicleFactory.create("CAR")runsCar.__init__. - Prototype — copies from EXISTING OBJECTS.
PrototypeRegistry.spawn("warrior")copies an existing Warrior.
Use Prototype when the interesting state is on the INSTANCE (specific stat values, specific inventory items), not just the class. If all that varies is the class name, Factory is simpler.
The interview trap
Interviewers sometimes describe a problem in a way that sounds Prototype-shaped but isn't. Watch for:
- "We need many similar objects" — is building actually slow, or are they cheap and just similar?
- "We have templates" — are they CLASS templates (Factory) or INSTANCE templates (Prototype)?
- "We want to customize each one" — how much? Small tweaks → Prototype. Big restructuring → Factory or Builder.
Ask the requirement questions from the objective before picking. That's the interview skill.
The decision cheat-sheet
| Situation | Pick |
|---|---|
| Cheap to build, no templates | No pattern needed |
| Cheap to build, templates | Factory + kwargs |
| Slow to build, only one instance | Cache + reuse (not Prototype) |
| Slow to build, few templates, big customization | Builder |
| Slow to build, few templates, small tweaks | Prototype |
| Slow to build, growing template set, dynamic | Prototype + Registry |