Prototype Pattern
The Prototype pattern builds a new object not by running its constructor but by byte-copying an existing, already-configured instance and then tweaking the copy — so the cost and complexity of constructing the original (parsing config, hitting a DB, deep object graphs) is paid once and amortised across every clone. In Java the copy is done by Object.clone(), which performs a field-by-field bit copy of the source object: that is the whole mechanism, and it is also the whole danger.
The mechanism: what super.clone() actually copies
Object.clone() allocates a new object of the same runtime class and copies each field's bits from source to copy. For a primitive (int, boolean) or an immutable reference (String), copying the bits is enough — there is nothing to share. But a field that holds a reference to a mutable object (an ArrayList, a Date, a nested mutable config) gets its pointer copied, not the object it points to. The clone and the original now hold the same reference — they are aliased. Mutating through one is visible through the other. This split is the named distinction the textbooks call shallow copy (copy the pointer) versus deep copy (copy the pointee too).
The original version of this page only had String and primitive fields, so its shallow clone was trivially safe and the “deep-copy bug” it warned about could never actually occur. Below we give the prototype a real mutable field — a list of accessories — so the hazard is concrete.
The naive (buggy) shallow clone
abstract class Car implements Cloneable {
protected String model;
protected String color;
protected List<String> accessories = new ArrayList<>(); // MUTABLE field
public abstract void customize(String color, String accessory);
// BUG: super.clone() copies the *reference* to accessories, not the list.
public Car shallowClone() {
try {
return (Car) super.clone(); // clone.accessories == this.accessories
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}Why the naive version is wrong: after super.clone(), the clone's accessories field points at the exact same ArrayList as the prototype's. Add a sunroof to one clone and every other clone — and the prototype itself — sprouts a sunroof, because there is only one list. The bug is silent: nothing throws, the corruption just leaks across objects that were supposed to be independent.
The correct deep clone (plus a Prototype Registry)
The fix: after super.clone() gives you the bit-copy, replace each mutable field with a fresh copy of its own. A registry then keeps named, pre-built prototypes and hands out a fresh clone on demand, so the client never names a concrete class.
abstract class Car implements Cloneable {
protected String model;
protected String color;
protected List<String> accessories = new ArrayList<>();
public abstract void customize(String color, String accessory);
@Override
public Car clone() {
try {
Car c = (Car) super.clone(); // 1. bit-copy all fields
c.accessories = new ArrayList<>(this.accessories); // 2. deep-copy the mutable one
return c; // primitives/String need no fix
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // can't happen: we implement Cloneable
}
}
}
class BasicCar extends Car {
public BasicCar() { model = "Basic"; color = "White"; accessories.add("ABS"); }
@Override public void customize(String color, String accessory) {
this.color = color;
this.accessories.add(accessory);
}
}
// Prototype Registry — client asks by key, gets an independent clone
class PrototypeRegistry {
private final Map<String, Car> protos = new HashMap<>();
public void register(String key, Car proto) { protos.put(key, proto); }
public Car create(String key) { return protos.get(key).clone(); }
}Go has no clone() and no inheritance, so the idiom is an explicit Clone() method on an interface — which makes the deep-copy step impossible to forget because you write every field:
type Car interface {
Clone() Car
Customize(color, accessory string)
}
type BasicCar struct {
Model, Color string
Accessories []string
}
func (b *BasicCar) Clone() Car {
cp := make([]string, len(b.Accessories))
copy(cp, b.Accessories) // deep-copy the slice; sharing the
return &BasicCar{b.Model, b.Color, cp} // backing array would be the same bug
}
func (b *BasicCar) Customize(color, accessory string) {
b.Color = color
b.Accessories = append(b.Accessories, accessory)
}Worked trace: same calls, two outcomes
Prototype starts as Basic/White with accessories = [ABS]. We make two clones, then call a.customize("Red", "Sunroof") on the first. The identity hash of the list (its object id) is printed so you can see whether lists are shared. These are the real values printed by the compiled program:
| Step | Action | Shallow clone result | Deep clone result |
|---|---|---|---|
| 1 | two clones made from prototype | a, b both point at list @705927765 | c → @366712642, d → @1829164700 |
| 2 | customize("Red","Sunroof") on first clone | adds to the one shared list | adds only to c's own list |
| 3 | inspect first clone | Basic/Red [ABS, Sunroof] | Basic/Red [ABS, Sunroof] |
| 4 | inspect untouched second clone | Basic/White [ABS, Sunroof] ❌ | Basic/White [ABS] ✓ |
| 5 | inspect the prototype itself | Basic/White [ABS, Sunroof] ❌ polluted | Basic/White [ABS] ✓ pristine |
In the shallow run, customizing one clone silently mutated a second clone and corrupted the prototype, because all three share the single list @705927765. In the deep run every object has its own list id and stays independent. Note that model and color never leak in either run — they are String, so a shallow copy of them is already safe. Only the mutable field needs the deep copy.
Pitfalls
- The shared-mutable-field aliasing bug (above). The default rule: any field that is not a primitive, a
String, or an immutable value type must be explicitly re-copied inclone(). Forgetting one field is the single most common Prototype defect. - Partial deep copy is worse than none. If
Carholds anEnginethat itself holds a mutableList<Sensor>, copying the list of engines but not each engine's sensor list re-introduces aliasing one level down. Deep copy must be recursive all the way to the leaves. Cloneableis a broken contract in Java. It is a marker interface with noclone()method;Object.clone()isprotectedand throws a checkedCloneNotSupportedException. Josh Bloch (Effective Java, Item 13) recommends avoiding it entirely in favour of a copy constructor or staticcopyOffactory, which sidestep the magic and letfinalfields stayfinal.- Clone bypasses constructors. Invariants you enforce in the constructor (validation, registering with a manager, incrementing a counter) do not run on a clone. If construction has side effects, cloning silently skips them.
- Cloning across an inheritance hierarchy.
super.clone()returns an object of the correct runtime class for free, but every subclass that adds a mutable field must overrideclone()to deep-copy it — easy to miss when a subclass is added later. - Resources that must not be duplicated. Open sockets, file handles, thread pools — a bit copy gives you two references to one OS resource; closing one breaks the other. Such fields need a fresh resource or must be excluded from the prototype.
When to use it / when NOT to
Reach for Prototype when: (1) constructing a fresh object is genuinely expensive or complex — it loads from disk/DB, runs heavy computation, or assembles a deep graph — and you need many near-identical instances (e.g. building an object from scratch parses a large config and takes ~50 ms, while clone() + a field tweak is ~0.5 ms; the 100× gap is the whole reason to keep a pre-built prototype around); (2) the concrete classes are decided at runtime and you want to add/remove producible types by registering instances rather than writing factory subclasses; (3) you have a fully-configured object and want a few variants without re-specifying the whole config.
Trade-offs versus the alternatives
- vs. Factory Method / Abstract Factory. A factory encodes how to build each product in a class hierarchy — add a product, add a subclass. Prototype encodes the product as a live instance in a registry — add a product, register an object, no new class. You gain runtime flexibility and fewer classes; you pay with the clone-correctness burden (every mutable field, forever) and the loss of constructor-enforced invariants. Choose Prototype when the set of products varies at runtime and copying a configured instance is cheaper than rebuilding; prefer a Factory when products are known at compile time and you want construction logic and validation centralised.
- vs. Builder. Builder assembles a complex object step by step from scratch; Prototype skips assembly by copying a finished one. Choose Prototype when you already have the object you want 90% of and need a tweakable copy; prefer Builder when each object is assembled fresh from many independent parameters.
- vs.
newplus a copy constructor. This is the real default for most code. A copy constructor (new BasicCar(existing)) is explicit, type-safe, plays well withfinalfields, and forces you to think about each field — it just can't pick the concrete class at runtime. Choose Prototype (with a registry) only when runtime type selection is the actual requirement; otherwise a copy constructor or staticcopyOffactory is simpler and safer.
Concrete decision: a level editor lets designers drag pre-tuned enemy types (each with HP, an AI script, a loot table) onto a map. The set of types grows as designers invent them, and each is an expensive-to-configure object. That is exactly runtime type selection + costly construction → register one tuned instance per enemy type and clone on drop. By contrast, an HTTP request object built fresh per call from a handful of headers wants a Builder or copy constructor, not a prototype registry.
Takeaways
Object.clone()is a field-by-field bit copy: safe for primitives and immutables, but it shares every mutable field's pointer — that aliasing is the pattern's defining hazard.- A correct
clone()callssuper.clone()then replaces each mutable field with its own deep copy, recursively to the leaves. Forgetting one field corrupts siblings and the prototype itself, silently. - Prototype's payoff is runtime type selection + cheap copies of expensive objects, usually via a registry; its cost is perpetual clone-correctness discipline and bypassed constructors.
- In Java, prefer a copy constructor or
copyOffactory overCloneableunless you specifically need to choose the concrete class at runtime.
Sources: Gamma, Helm, Johnson & Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Prototype, pp. 117–126); Joshua Bloch, Effective Java 3rd ed., Item 13 (“Override clone judiciously”); Refactoring.Guru, Prototype. Java and Go examples compiled and run for this guide; printed object-identity hashes are from the actual program output. Re-authored and deepened for this guide — added a mutable field so the shallow-vs-deep aliasing bug is demonstrated end-to-end, a corrected deep clone(), a Prototype Registry, a traced before/after example, and a selection & trade-offs section.
🤖 Don't fully get this? Learn it with Claude
Stuck on Prototype Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Prototype Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Prototype Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Prototype Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Prototype Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Prototype Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.