Prototype Pattern

Clone an existing object instead of constructing from scratch. Shallow vs deep copy and when each is correct.

Pattern Intermediate
10 min read
pattern creational prototype cloning deep-copy

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 Letter prototype with the company letterhead pre-filled; cloning produces a new draft that you customise.
  • Game entities — an Enemy prototype 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 RequestConfig prototype 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 than prototype.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:

  • Cloneable is broken. It is a marker interface; Object.clone() is protected and 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 same Address instance. This is the single most common Prototype bug.
  • Deep copy must descend through every mutable field. Skipping one — a Map, a Date, an inner list — silently re-introduces shared mutable state.
  • Immutable fields are safe to share. String, Integer, LocalDate, anything final with final fields. 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#

VariantMechanismWhen it fits
Shallow cloneCopy fields by reference; mutable nested objects shared.The prototype’s nested objects are immutable, or sharing is intentional.
Deep cloneRecursively copy every mutable field.The default safe choice — the new object must be fully independent.
Copy constructornew Foo(other) — explicit, contract-checked, the Java idiom.The recommended Java approach. Replaces Cloneable / clone().
Static copy factoryFoo.copyOf(other) — same as copy constructor with a clearer name.When the call site benefits from a verb (copyOf, from).
Serialization-basedRound-trip through ObjectOutputStream / JSON.Arbitrary graphs, especially with cycles; slow but correct.
Registry-backedA 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 Prototype reference 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.
  • Cloneable is a trap. Implementing Cloneable and overriding clone() is technically possible but riddled with gotchas (the protected modifier, CloneNotSupportedException on 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 FileInputStream would have two objects pointing at the same OS file descriptor. Either reject cloning for such classes or transient-out the resources and re-acquire on clone.

Cloneable / Object.clone()

  • Marker interface plus a protected method.
  • 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.
  • 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.
Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.