Design patternCreational Patterns
Prototype
Create new objects by copying a configured existing instance, so callers get a ready-made variant without knowing its class or setup.
- Complexity
- Low
- Seen in the wild
- Medium
- Also known as
- Clone
Intent
Make an object responsible for copying itself. Clients keep a set of preconfigured instances, the prototypes, and create new objects by cloning one and adjusting the copy, instead of calling a constructor and repeating the setup.
Forces
- Expensive or detailed setup vs many similar objects: rebuilding the same configuration by hand for every instance is slow and error-prone.
- Copying from outside vs encapsulation: a client can't copy private fields it can't see, and shouldn't need to know them.
- Shallow copy vs independent copies: copying references to nested lists and maps makes the clone and the original share state.
- Configuration known at runtime vs classes known at compile time: templates loaded from config files don't map to one subclass each.
Therefore
Give the object a clone() method that returns an independent copy of itself, and create variants by cloning a registered template and changing the copy.
The object knows its own fields, so it is the right place to copy them, including private ones. A small registry maps names to prototypes. Before writing clone() by hand, check what the language already gives you: copy.deepcopy in Python, copy constructors or records in Java, struct assignment plus maps.Clone and slices.Clone in Go, spread syntax or structuredClone in JavaScript. The pattern's value is the template registry idea and the guarantee of a correct deep copy, not the method name.
Structure
Diagram source (Mermaid)
classDiagram
class Prototype {
<<interface>>
+clone() Prototype
}
class AlertRule {
-string name
-int threshold
-Map labels
-string[] notify
+clone() AlertRule
}
class RuleCatalog {
-Map templates
+register(key, rule) void
+create(key) AlertRule
}
class Client {
+provision(service) AlertRule
}
Prototype <|.. AlertRule
RuleCatalog o-- Prototype : templates
Client ..> RuleCatalog : asks for copiesParticipants
- Prototype (
Prototype): declaresclone(). Often unnecessary as a separate type when the language has a copy convention. - Concrete prototype (
AlertRule): copies itself, including deep copies of mutable collections. - Registry (
RuleCatalog): optional; stores named prototypes and hands out clones, never the originals. - Client: asks for a clone by name and customizes the copy.
Use it when
- Many objects differ from a few templates only in a handful of fields, and the templates come from configuration or user input.
- Creating an object from scratch is expensive (parsing, network calls, computed defaults) but copying a ready one is cheap.
- Client code must copy objects whose concrete class it doesn't know, for example items behind an interface in an editor or a workflow designer.
- You want "duplicate" or "save as new" behavior and the object owns private state the caller can't reach.
Do not use it when
- The state is plain data and a copy has no rules of its own. The built-in copiers cover it: Python's
copy.copy/copy.deepcopyanddataclasses.replace, Java records with wither methods, Go struct assignment, and JavaScript spread orstructuredClone. Prototype earns itsclone()only when copying needs rules they don't know - redacting fields, regenerating identity, deep-copying selected references. - The object is immutable. Share the instance; there is nothing to copy. To get a variant, create a new value with the changed field (
replace(rule, threshold=1500)). - The object holds resources that can't be duplicated meaningfully: open connections, file handles, locks, or identity such as database primary keys. Cloning these causes subtle bugs. Use a factory that builds fresh ones.
- There are only a few fixed variants known at compile time. A factory function per variant is clearer than a registry of instances.
- In Java, don't reach for
CloneableandObject.clone(). They skip constructors, copy shallowly by default and need casts. Write a copy constructor or a staticcopyOf.
Application grill
Inspect first: the expensive setups that would be copied (configured clients, parsed config, game objects); the language's cloning support (structuredClone, copy constructors, clone traits); which fields hold non-copyable resources (sockets, connections, timers) today.
Missing pieces: none beyond the language: structuredClone or a copy constructor. A deep-copy library only when the graph is cyclic or copying must skip typed fields.
Ask before you build:
- Scale: what does a copy cost versus a rebuild, measured?
- Performance: do copies run per request or per element where allocation shows?
- Reliability: which fields must be excluded or reset in copies (connections, caches, clock handles), and who owns that list?
- Budget: is a prototype pool actually cheaper than constructing fresh?
- Implementation: is the copy shallow or deep per field, and where is that documented?
Implementation steps
- Decide what "independent copy" means for each field: immutable values can be shared, mutable collections and nested objects must be copied, and identity fields (IDs, handles) must be reset or excluded.
- Implement
clone()on the class, or a copy constructor, using the language's copy facilities where they are correct. - Write a test that mutates every collection on the clone and asserts the original is unchanged.
- If templates are named, add a registry that stores prototypes and always returns clones from
create(). - Load or register the templates once at startup, and replace hand-repeated setup code with
create(key)plus the field changes.
Example
A monitoring platform ships alert rule templates ("high latency", "error spike"). Teams provision rules for each service by copying a template and adjusting labels, thresholds and notification targets. Templates must never change when one team edits its copy.
interface Prototype<T> {
clone(): T;
}
class AlertRule implements Prototype<AlertRule> {
constructor(
public name: string,
public metric: string,
public threshold: number,
public forMinutes: number,
public labels: Map<string, string>,
public notify: string[],
) {}
// structuredClone would drop the class (and its methods); copy explicitly.
clone(): AlertRule {
const { name, metric, threshold, forMinutes } = this;
return new AlertRule(name, metric, threshold, forMinutes, new Map(this.labels), [...this.notify]);
}
}
class RuleCatalog {
private readonly templates = new Map<string, AlertRule>();
register(key: string, rule: AlertRule): void {
this.templates.set(key, rule);
}
create(key: string): AlertRule {
const template = this.templates.get(key);
if (!template) throw new Error(`unknown template: ${key}`);
return template.clone();
}
}
// Templates usually come from config at startup.
const catalog = new RuleCatalog();
const labels = new Map([["severity", "page"], ["team", "platform"]]);
catalog.register("high-latency", new AlertRule("high-latency", "http_p99_ms", 800, 5, labels, ["oncall:platform"]));
const checkout = catalog.create("high-latency");
checkout.name = "checkout-latency";
checkout.labels.set("service", "checkout");
checkout.notify.push("chat:#checkout");
const search = catalog.create("high-latency");
search.name = "search-latency";
search.threshold = 1500;
console.log(checkout.labels.size, checkout.notify.length); // 3 2
console.log(search.labels.size, search.notify.length, search.threshold); // 2 1 1500Trade-offs
Gains
- New variants come from data (registered templates), not from new subclasses.
- Clients copy objects without knowing their concrete class or private fields.
- Expensive setup runs once per template, not once per instance.
Costs
- Correct deep copying is the whole job, and it is easy to get wrong: one forgotten collection and clones share state.
- Cyclic references, shared sub-objects and identity fields need explicit rules that generic copy helpers can't infer.
- Every new mutable field must be added to
clone(). A test that mutates the copy is the only reliable guard.
Relations
- Factory Method needs a creator subclass per product. Prototype replaces that hierarchy with registered instances.
- Abstract Factory can store one prototype per product kind and clone them, so a new family is new data rather than a new class.
- Builder assembles an object from nothing. A builder seeded from a prototype gives "like this one, but" construction with validation.
- Composite trees are often cloned as a whole, which forces a recursive deep copy through every child.
- Memento snapshots state to restore it later. A clone can serve as a simple memento when the object has no external resources.
- Flyweight is the opposite move: share one instance instead of copying it, which only works for immutable state.
Agent checklist
- You checked the language's built-in copy facilities, and used them unless they copy too shallowly or too much.
- Every mutable collection and nested object is copied; immutable values and true shared services are not.
- Identity fields, locks, connections and handles are reset or excluded from the copy.
- A test mutates each collection on a clone and asserts the original and the registered template are unchanged.
- The registry returns clones only and never exposes the stored prototypes.
See also
- Factory MethodLet a subclass or a supplied function decide which concrete object to create, so shared workflow code depends only on an interface.
- Abstract FactoryCreate families of related objects through one interface so client code always gets a matching set and never names the concrete classes.
- BuilderAssemble a complex object through named, step-by-step calls and validate it once in build(), instead of a constructor with many parameters.
- CompositeTreat single objects and nested groups of them through one interface, so code can walk a tree of any depth without checking node types.
- MementoCapture an object's internal state in an opaque snapshot so it can be restored later without exposing that state to the code holding the snapshot.
- FlyweightShare one immutable object for state that many objects have in common, and pass the per-object rest in, to cut memory when instances number in the millions.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.