Try to duplicate an object you hold behind an interface and new abandons you immediately: you can't construct what you can't name, and the variable's type tells you Shape, not which shape. Prototype is the Gang of Four creational pattern that makes objects responsible for copying themselves, so new instances come from existing ones instead of from constructors. The catalog entry boils down to one move: keep a configured instance around and make new objects by cloning it.
A prototype is really just an object with a virtual clone() method. Virtual is the load-bearing word, because dispatch sends the call to the object's actual class, the one place in the program that knows every field and can copy them all. The same move pays off when construction is expensive: build the costly thing once, then stamp copies instead of re-running the setup.
Here's the pattern at its smallest. Shape declares clone() alongside its normal operations, Circle implements it with its own copy constructor call, and the duplicate() function works on any Shape without naming one. The example clones a circle at (10, 20) and moves the copy to x = 99. Before reading past the code, predict both describe() outputs:
The original still reports circle r=5 at (10, 20) and the copy reports circle r=5 at (99, 20), two independent objects from one clone() call. The detail worth a second look is duplicate(): it copies a circle correctly while containing no mention of circles. Add a Square class tomorrow and duplicate() copies squares too, unedited, because the class-specific knowledge lives inside each class's own clone().
Now the part that pages people at 2am. A copy is shallow when it duplicates the object's fields but shares whatever those fields point at, and shallow is what you get by default almost everywhere: Java's Object.clone() documents itself as copying fields without cloning their contents, and Python's copy module makes the split explicit by shipping copy.copy() and copy.deepcopy() as separate functions. The document below clones both ways. Predict what original.tags prints after the shallow clone appends "draft":
The original prints ['finance', 'draft']. It changed, because the shallow clone copied the Document object while sharing the one tags list between both documents, so the append landed in a list the original also holds. The deep clone's append leaves the original alone. What counts as shallow shifts by language, and the code tabs above carry the differences: Go slices share their backing array through a struct copy, C++ copies members by value so the trap needs a pointer field to appear, and Rust's derive(Clone) copies owned data deeply, so sharing has to be opted into with Rc.
The cast is the smallest in the creational family: a Prototype interface declaring clone(), ConcretePrototypes that implement it (Circle), and a Client that copies through the interface (duplicate()). One optional role earns its keep in practice: a prototype registry, a plain map from names to configured exemplar objects. Code asks the registry for "invoice" and gets a clone of the stored exemplar, which turns object variants into data instead of subclasses.
The pattern works like a photocopier: feed it any page and it reproduces the page without understanding a word of the content, the way duplicate() copies shapes it can't name. The analogy breaks exactly where the last section did. Paper is one layer deep, but objects have depth, fields pointing at other objects, and the copier has no opinion on whether to duplicate those or share them. That decision is yours per field, and the shallow default most languages hand you is an answer chosen by omission.
Installing the pattern is four moves:
Shape declares clone() right next to the operations everyone already uses. That placement is the pattern: copying becomes something you can ask of any shape you hold, not just the ones you can name.Circle.clone() returns Circle(self.radius, self.x, self.y), and only Circle could write that line, because only it sees its own fields. The knowledge of how to copy lives with the thing being copied.registry["invoice"].clone() on demand. New variants become registry entries instead of new classes.Go expresses the pattern with a struct dereference. copied := *c copies every field by value in one line, and wrapping that in a Clone() Shape method makes it polymorphic. The honesty cost is the slice: a Go slice is a small header pointing at a backing array, so the one-line copy duplicates the header and shares the array, which is the shallow trap with Go-specific spelling. Deep copies of slices and maps are written by hand with make and copy, and the language ships no general deep copy at all.
Rust turned the pattern into a one-line derive. The Clone trait generates a member-wise copy, and because Vec and String clone their contents, the derived copy is deep wherever data is owned. Sharing can't happen by accident: it requires reaching for Rc visibly in the type. One wrinkle matters for the GoF shape: Clone isn't object-safe, so copying through a trait object takes an explicit clone_box() method like the one in the code block, a small tax for dispatching the copy at runtime.
You might think clone() is a copy constructor with extra ceremony, and at a single call site with a known class, it is: Circle(original) says the same thing more directly. The difference is who knows the class. A copy constructor must be invoked by name, so the caller commits to Circle at compile time. A clone() call dispatches on the object's runtime type, which is why duplicate() can copy shapes it has never heard of. One is a tool for call sites that know, the other for call sites that can't.
Java's built-in attempt is the cautionary tale here. Object.clone() arrives protected, requires implementing the empty Cloneable marker interface to not throw CloneNotSupportedException, and still hands back a shallow copy. Java style guides have steered people toward copy constructors and static factory copies instead for years, which is a useful reminder that the pattern is the idea, not any particular language's built-in for it.
The method is short and the failure modes are quiet, so the discipline is mostly about what clone() doesn't say:
Two questions before you go. First: clone() and a copy constructor both produce copies, so what can duplicate(shape) do that Circle(original) can't, and what does the caller have to lack for that difference to matter? Second: your shallow clone shares its tags list with the original. Name two ways to fix it and the cost each one carries. Both answers are on this page, one per code block.
Then go spot it in the wild. Python ships the pattern as the copy module, where __copy__ and __deepcopy__ are clone() by other names. JavaScript added structuredClone() as a built-in deep copy that even survives circular references. Last, find a class in your own codebase with a duplicate() or copy() helper and audit it field by field, asking of each list and nested object: copied, or shared? If nobody can answer from the code, that's the bug waiting.
from abc import ABC, abstractmethod
class Shape(ABC): # Prototype: declares the copy operation
@abstractmethod
def clone(self) -> "Shape": ...
@abstractmethod
def move_to(self, x: int, y: int) -> None: ...
@abstractmethod
def describe(self) -> str: ...
class Circle(Shape): # ConcretePrototype: knows how to copy itself
def __init__(self, radius: int, x: int, y: int):
self.radius = radius
self.x = x
self.y = y
def clone(self) -> "Shape":
return Circle(self.radius, self.x, self.y)
def move_to(self, x: int, y: int) -> None:
self.x = x
self.y = y
def describe(self) -> str:
return f"circle r={self.radius} at ({self.x}, {self.y})"
def duplicate(shape: Shape) -> Shape:
# No Circle mentioned: the object knows how to copy itself
return shape.clone()
# Example usage
original = Circle(5, 10, 20)
copy = duplicate(original)
copy.move_to(99, 20)
print(original.describe()) # circle r=5 at (10, 20)
print(copy.describe()) # circle r=5 at (99, 20)import copy
class Document:
def __init__(self, title: str, tags: list[str]):
self.title = title
self.tags = tags
def clone_shallow(self) -> "Document":
# Copies the object, but SHARES the tags list
return copy.copy(self)
def clone_deep(self) -> "Document":
# Recursively copies the tags list too
return copy.deepcopy(self)
# Example usage
original = Document("Q3 Report", ["finance"])
shallow = original.clone_shallow()
shallow.tags.append("draft")
print(original.tags) # ['finance', 'draft'] <- the original changed
deep = original.clone_deep()
deep.tags.append("internal")
print(original.tags) # ['finance', 'draft'] <- unchanged this time