Design Patterns: Creational · Creational Patterns

Prototype Pattern

Creating new objects by cloning an already-initialized one, instead of repeating expensive setup.

  • 6 min read
  • 10 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. A registry of prototypes
  4. Where you'll see it
  5. Choosing among the creational patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

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:

  1. The pattern: a copy() method that decides, field by field, what to share.
  2. Shallow versus deep copies, in Java and Python.
  3. A registry of prototypes, and why it copies on the way in and out.
  4. 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 forBecause
Exactly one shared instance, by designSingletonTwo copies would be a bug (e.g. two connection pools fighting over the same database connections).
One place that decides which concrete class to buildFactory MethodCallers depend on an interface; adding a type changes the factory, not every caller.
A matched family of related objects that must vary togetherAbstract FactorySwapping the factory swaps the whole family, so mismatched pieces can't be combined.
An object with many optional parts, assembled step by stepBuilderNamed, optional steps instead of long positional constructors; validate once in build().
Many near-identical objects that are expensive to set upPrototype (this lesson)Copy a ready-made template instead of repeating costly construction.

Common mistakes

MistakeWhat happensDo this instead
A shallow copy of an object with mutable fieldschanges to one copy appear in the otherscopy each mutable field explicitly
A deep copy of everythingthe expensive part you meant to share is copied toodecide per field: share immutables, copy mutables
Using Java's clone() / Cloneableno constructor runs; final fields can't get fresh copiesa copy constructor or copy() method
Handing out the registry's template itselfa spawned object changes the templatecopy on the way in and on the way out
Prototype for objects that are cheap to buildcopying decisions with nothing savedjust 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 String can safely be shared.
  • A copy constructor or a copy() method is usually clearer and safer than Object.clone() - in Python, a copy() method that decides field by field, since copy.deepcopy also copies what should stay shared.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You needUse
Many near-identical objects, expensive to set upPrototype: copy a template
Copy an objecta copy constructor / copy() that decides per field
Copy an array (Java)array.clone() - one level
Share a big immutable part across instancesFlyweight (share it in copy())
Variations of an immutable objectwithX(...) / dataclasses.replace - no copy of the rest
Scarce resources (connections, threads)a pool, not a prototype

Snippets worth knowing by heart

Java

private GameCharacter(GameCharacter other) {    // copy constructor
    this.model = other.model;                   // immutable: share
    this.items = new ArrayList<>(other.items);  // mutable: copy
    this.name = other.name;
}
GameCharacter copy() { return new GameCharacter(this); }

Python

def copy(self) -> "GameCharacter":
    c = copy.copy(self)              # shallow: every field shared
    c.items = list(self.items)       # give the mutable field its own list
    return c

def __deepcopy__(self, memo):        # keep deepcopy from duplicating the model
    c = copy.copy(self)
    c.items = copy.deepcopy(self.items, memo)
    return c

The traps

  • Shallow copies of mutable fields - copies change each other.
  • Deep copies of everything - the expensive part is copied too.
  • clone() / Cloneable - no constructor, final fields stuck.
  • Handing out the template - copy on the way in and out.

Staff-level points, one line each

  • Neither clone() nor copy.copy/deepcopy runs the constructor - a counter stayed at 1; ids and validation are skipped.
  • Cloneable is a method-less marker; clone() is protected with a checked exception - prefer copy constructors; arrays are the exception.
  • Clones copy one level: an int[][] clone or grid.copy() shares the rows.
  • deepcopy's memo keeps shared references shared and survives cycles - but copies everything and fails on locks (cannot pickle '_thread.lock' object).
  • Deep-copying a unit with its 50,000-element model was hundreds of times slower than sharing the model via __deepcopy__.
  • Prototype + Flyweight: share immutable intrinsic state, copy per-instance state.
  • Copy-on-write (fork, CopyOnWriteArrayList) is a lazy copy; immutability removes the copy altogether.

Say it in an interview

  • "The model is immutable, so copies share it; the inventory list is copied."
  • "I'd write a copy constructor rather than implement Cloneable."
  • "The registry copies templates on the way in and out, so nothing can change them."
  • "If the cost is a database connection, that's a pool, not a prototype."

Practice

This skill comes with 10 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Prototype Pattern free

Clone a Document Without Sharing Mutable State

MediumProCoding problemJava · Python

Implement Document.clonePrototype() so it returns a copy of the document whose tags list is fully independent of the original's - mutating the clone's tags must never affect the original's tags, and vice versa.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Spawn Game Units From a Prototype Registry

MediumProCoding problemJava · Python

A game defines unit templates once (expensive in a real game) and spawns every unit by copying a template. Right now copy() just returns this, so every spawned unit is secretly the same object as its template - teaching one archer a new ability teaches all of them.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Prototype Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime