Prototype Pattern
Clone an existing object instead of constructing from scratch. Shallow vs deep copy and when each is correct.
What it is#
The Prototype pattern creates new objects by cloning an existing instance — the prototype — rather than constructing from scratch. The Gang of Four put this in the creational chapter because the question it answers is “how do you make a new object?” and the answer it gives is “ask an existing one to copy itself.”
The shape is small. A Prototype interface declares a clone() method; concrete classes implement it. The system keeps a registry — or just a few field references — of pre-configured prototype instances. To make a new object, you call clone() on the prototype that most closely resembles what you want, then tweak the differences.
The pattern shines in two situations: when construction is expensive (lots of computation, network calls, file reads) and the new object is mostly the same as one that already exists; and when the concrete class is unknown to the caller at compile time but a sample instance is in hand. The price is that you have to get cloning right — and in Java, “right” means understanding shallow vs deep copy, and knowing that Cloneable is a famously broken interface.
Class structure#
┌──────────────────────┐ │ Prototype │ ├──────────────────────┤ │ + clone() : Prototype│ └──────────▲───────────┘ │ ┌──────────┴────────────┐ │ │ ┌────┴─────────────┐ ┌──────┴───────────┐ │ ConcretePrototype│ │ ConcretePrototype│ │ 1 │ │ 2 │ ├──────────────────┤ ├──────────────────┤ │ + clone() │ │ + clone() │ └──────────────────┘ └──────────────────┘ ▲ │ clones / registers │ ┌────────┴─────────┐ │ Client │ │ (prototype reg.) │ └──────────────────┘The client holds prototype instances and produces new objects by cloning them — never by calling new on the concrete type.
When to use it#
Reach for Prototype when:
- Object construction is expensive (parsing, network, large allocations) and most of the work is the same across instances you need.
- You need to create objects whose concrete class is decided at runtime by configuration or by another object you hold a reference to.
- You want many variants that differ only in a few fields from a known baseline — pre-loaded templates, default-configured widgets, snapshot copies.
- You need an undo / snapshot mechanism — clone the current state, mutate the original, restore the clone if needed (the Memento pattern is the strict snapshot variant — not in this workbook’s pattern set).
Concrete shapes this takes:
- Document templates — a
Letterprototype with the company letterhead pre-filled; cloning produces a new draft that you customise. - Game entities — an
Enemyprototype with stats; spawning ten copies clones the prototype rather than re-reading the config file ten times. - JavaScript-style object models — every JS object has a prototype chain. Prototype, the pattern, is the language feature in JS.
- Configuration baselines — a
RequestConfigprototype for production; clone-and-tweak for a one-off retry policy. - CAD / vector editors — duplicating a shape clones its style attributes and geometry.
When not to use it:
- When construction is cheap.
new Foo()is simpler thanprototype.clone(). - When the object is immutable. There is nothing to copy — share the reference.
- When the object has identity that should not be cloned (database rows with primary keys, network connections, open file handles).
How it works#
Two implementations in Java, illustrating shallow vs deep copy. We avoid Cloneable and use a copy constructor instead — the recommended Java idiom.
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;
// Mutable inner object — the kind of thing that makes shallow copy dangerous.public final class Address { private String street; private String city;
public Address(String street, String city) { this.street = street; this.city = city; }
// Copy constructor — the Java-idiomatic way to clone. public Address(Address other) { this.street = other.street; this.city = other.city; }
public void setStreet(String s) { this.street = s; } public String street() { return street; } public String city() { return city; }}
// The prototype. Has both a primitive field, a mutable object reference, and a collection.public final class Employee { private String name; private int salary; private Address address; // mutable reference private List<String> skills; // mutable collection
public Employee(String name, int salary, Address address, List<String> skills) { this.name = name; this.salary = salary; this.address = address; this.skills = skills; }
// SHALLOW copy — same `Address` instance, same `List` instance. public Employee shallowCopy() { return new Employee(this.name, this.salary, this.address, this.skills); }
// DEEP copy — new `Address`, new `List` with the same string elements. // Strings are safe to share because they are immutable. public Employee deepCopy() { Address addrCopy = new Address(this.address); List<String> skillsCopy = new ArrayList<>(this.skills); return new Employee(this.name, this.salary, addrCopy, skillsCopy); }
public void setName(String n) { this.name = n; } public void promote(int newSalary) { this.salary = newSalary; } public Address address() { return address; } public List<String> skills() { return skills; }}
// A registry — the optional but common companion to the pattern.public final class EmployeeRegistry { private final Map<String, Employee> prototypes = new HashMap<>();
public void register(String key, Employee prototype) { prototypes.put(key, prototype); }
public Employee newFrom(String key) { Employee proto = prototypes.get(key); if (proto == null) throw new IllegalArgumentException("no prototype: " + key); return proto.deepCopy(); }}
// Demonstration of the shallow-vs-deep difference.//// Employee proto = new Employee(// "intern-template", 50_000,// new Address("1 HQ Way", "Bangalore"),// new ArrayList<>(List.of("git", "java")));//// Employee shallow = proto.shallowCopy();// shallow.address().setStreet("2 New Way");// // proto.address().street() is now "2 New Way" — they share the Address!//// Employee deep = proto.deepCopy();// deep.address().setStreet("3 Other Way");// // proto.address().street() is unchanged — independent Address.Five things worth noticing:
Cloneableis broken. It is a marker interface;Object.clone()isprotectedand does a shallow field-by-field copy that ignores constructors. The recommended idiom in Effective Java (Item 13) is copy constructors or static copy factories — both shown above.- Shallow copy shares mutable references. Modifying
shallow.address()modifies the prototype’s address because both hold the sameAddressinstance. This is the single most common Prototype bug. - Deep copy must descend through every mutable field. Skipping one — a
Map, aDate, an inner list — silently re-introduces shared mutable state. - Immutable fields are safe to share.
String,Integer,LocalDate, anythingfinalwithfinalfields. Copying them is wasted memory, not safety. - Serialization is the “lazy deep copy.” Write to bytes, read back — the deserialised object is a deep copy. Slow, but it works for arbitrary graphs including cycles.
A serialization-based deep copy, when you cannot or will not write copy constructors all the way down:
import java.io.*;
public static <T extends Serializable> T deepCopyViaSerialization(T original) { try (var baos = new ByteArrayOutputStream(); var oos = new ObjectOutputStream(baos)) { oos.writeObject(original); try (var bais = new ByteArrayInputStream(baos.toByteArray()); var ois = new ObjectInputStream(bais)) { @SuppressWarnings("unchecked") T copy = (T) ois.readObject(); return copy; } } catch (IOException | ClassNotFoundException e) { throw new IllegalStateException("deep copy failed", e); }}Use it sparingly. It is correct, it handles cycles, and it is one to two orders of magnitude slower than hand-written copy constructors. JSON or Jackson round-trips are popular variants of the same trick.
Variants#
| Variant | Mechanism | When it fits |
|---|---|---|
| Shallow clone | Copy fields by reference; mutable nested objects shared. | The prototype’s nested objects are immutable, or sharing is intentional. |
| Deep clone | Recursively copy every mutable field. | The default safe choice — the new object must be fully independent. |
| Copy constructor | new Foo(other) — explicit, contract-checked, the Java idiom. | The recommended Java approach. Replaces Cloneable / clone(). |
| Static copy factory | Foo.copyOf(other) — same as copy constructor with a clearer name. | When the call site benefits from a verb (copyOf, from). |
| Serialization-based | Round-trip through ObjectOutputStream / JSON. | Arbitrary graphs, especially with cycles; slow but correct. |
| Registry-backed | A Map<Key, Prototype> returns clones on demand. | Many prototypes, looked up by string/enum key — template galleries, entity spawning. |
The copy constructor is the recommended default in modern Java. Reach for serialization-based cloning only when the object graph is too deep or too cyclic to copy by hand, and accept the performance cost.
Example systems#
The pattern surfaces in several places:
java.lang.Object.clone()— the JDK’s original (and broken) implementation of the pattern. Many JDK classes (ArrayList,HashMap,Date) override it to do something useful, but the base mechanism is a cautionary tale.Cloneable— the marker interface that signals “Object.clone() should not throw on this class.” Famously called out as broken in Effective Java.- JavaScript’s
Object.create(prototype)— Prototype as a language feature; every object has a[[Prototype]]slot. - Game engines (Unity, Unreal) — “prefab” / “blueprint” objects are prototypes; placing one in a level clones it.
- Spring’s prototype-scoped beans —
@Scope("prototype"); the container clones (well, re-constructs from definition) on every injection. Same intent, different mechanism. - CAD / vector tools — every “duplicate” command is the pattern.
Trade-offs#
What you gain:
- Skip expensive construction. Cloning a parsed config object is a few field copies; re-parsing it is milliseconds.
- Runtime polymorphism without knowing the class. The caller holds a
Prototypereference and clones it; the concrete type is set up elsewhere. - Easy variant generation. Configure one baseline; clone-and-tweak for the dozen near-duplicates.
- Composes with Abstract Factory. Prototype-based factories register one instance per product and clone on demand — fewer subclasses, more flexibility.
What you pay:
- Cloning correctness is your job. Every mutable field is a potential shallow-copy bug. Adding a new mutable field to a class with a copy constructor without updating the constructor is a silent regression.
- Circular references break naive deep copy. Cycles need either a visited-set or serialization. Hand-written copy constructors do not handle them.
- Identity is awkward. A clone has the same fields but is not the same object. Database identity, equality semantics, registry keys — all need attention.
Cloneableis a trap. ImplementingCloneableand overridingclone()is technically possible but riddled with gotchas (theprotectedmodifier,CloneNotSupportedExceptionon a class that can clone, no constructor invocation). Skip it; use copy constructors.- Open file handles, sockets, threads — do not clone. A clone of a
FileInputStreamwould have two objects pointing at the same OS file descriptor. Either reject cloning for such classes ortransient-out the resources and re-acquire on clone.
Cloneable / Object.clone()
- Marker interface plus a
protectedmethod. - Bypasses constructors — invariants are not re-checked.
- Shallow by default; deep cloning requires manual descent.
- Declares
CloneNotSupportedException. - Famously broken; Effective Java recommends against it.
Copy constructor / static copy factory
- Plain Java; no special interface.
- Goes through a real constructor; invariants validated.
- Author writes the copy depth explicitly — no surprises.
- No checked exceptions.
- The recommended idiom in modern Java.
Related patterns#
- Factory Method Pattern — Factory Method creates by subclass dispatch; Prototype creates by cloning an instance. Both produce objects whose concrete type the caller does not name.
- Abstract Factory Pattern — a prototype registry is one way to implement Abstract Factory: register one product per family and clone on
createX(). - Builder Pattern — Builder constructs from parts; Prototype copies from a finished example. They compose: clone a prototype, then mutate via a builder-like API.
- Singleton Pattern — the inverse philosophy. Singleton hands out the same instance; Prototype hands out a copy.
- Composite Pattern — deep-cloning a Composite tree is a common Prototype use case; serialization-based copy is often the cleanest implementation.