AcademyDesign Patterns
Prototype pattern
Cloning a configured instance instead of rebuilding from scratch, the per-field copy it costs, nested fields included, and a shallow clone() copying a reference, not a value.
6 min readUpdated 3 exercises
Prototype is for when building from scratch is the wrong move entirely, when copying an object you already have, configured, is cheaper than reconstructing one from raw parameters.
Some objects are expensive or awkward to construct from raw parameters. They were loaded from a file, tuned by hand over several calls, or assembled through a Builder already. Prototype skips reconstructing one of those from scratch: it clones an existing, already-configured instance instead, then lets the caller tweak the copy.
The problem it solves
You are building a game with hundreds of goblins. Every goblin has the same stats, the same loot table and the same behavior, loaded from a configuration file and validated on the way in.
If each new goblin is built from scratch, you reload and revalidate the same data hundreds of times. And if the setup involved a slow step, such as a network call, a database read or a heavy computation, you pay that price every time.
The same shape appears outside games:
- A user clicks "duplicate" on a document, a challenge or a dashboard, and expects an independent copy.
- A test needs a fresh copy of the same fixture, so one test's edits do not leak into the next.
- An app keeps a snapshot of a form's state so it can undo changes.
The fix is to build one well-configured prototype and produce every other instance by copying it.
Structure
Two parts:
- The prototype: an existing, configured object.
- A `clone()` operation that returns a new, independent object with the same state. The class copies itself, so the caller never needs to know its fields or its concrete type.
class Enemy {
constructor(kind, hp, loot) {
this.kind = kind;
this.hp = hp;
this.loot = loot;
}
clone() {
return new Enemy(this.kind, this.hp, [...this.loot]);
}
}
const goblinTemplate = new Enemy('goblin', 20, ['dagger']);
const goblin2 = goblinTemplate.clone();
goblin2.hp = 25; // tweak the copy — the template stays untouchedThe [...this.loot] (list(self.loot) in Python) is doing real work, not decoration. Copy the array reference instead of its contents and goblin2.loot.push(...) would silently mutate the template every future clone starts from. That is the shallow versus deep copy trap this pattern lives or dies by: a Prototype whose clone() shares a nested object with the original is not a working prototype, it is a second name for the same data.
Shallow or deep
| Shallow copy | Deep copy | |
|---|---|---|
| Top-level fields | Copied | Copied |
| Nested objects and arrays | Shared by reference | Copied recursively |
| Cost | Cheap | Proportional to the size of the data |
| Risk | A change to a nested value shows up in every copy | Slow for large graphs, trouble with cycles |
| JavaScript tools | Spread { ...obj }, Object.assign, slice() | structuredClone(obj) |
Choose deliberately. If nested data is never modified after cloning, sharing it is a fine optimization. If it can change, copy it.
Where it is used in real systems
| Use case | What is cloned |
|---|---|
| Duplicate and templates | "Duplicate this challenge", a project template, a document from a preset. |
| Immutable state updates | React and Redux copy state with spread, then change one field. |
| Undo and history | A snapshot of the state before each change. |
| Test fixtures | A base object copied and adjusted per test, so tests stay independent. |
| Game objects and particles | Enemies, bullets and effects cloned from one configured template. |
| Configuration presets | A base config copied and overridden per environment or tenant. |
| Caching defaults | A costly default object built once and copied on demand. |
Prototypes you already use
JavaScript is built on this idea, which makes it a favorite interview topic.
- `Object.create(proto)` makes a new object whose prototype is an existing one. Every JavaScript object inherits through a prototype chain.
- `structuredClone(value)` deep copies plain data, including nested objects, arrays,
Map,SetandDate. - Spread and `Object.assign` make shallow copies, the everyday form of the pattern in React reducers.
Be careful with JSON.parse(JSON.stringify(value)), the copy trick many people reach for. It quietly drops undefined and functions, turns Date into a string, and throws on cycles. Prefer structuredClone, which keeps those types.
structuredClone has limits too: it cannot copy functions, and class instances come back as plain objects without their methods. That is why classes usually implement their own clone(), as in the example above.
When not to use it
- Construction is cheap. If
new Enemy(...)takes microseconds, cloning buys nothing and adds a method to maintain. - Objects hold resources. Open connections, file handles and sockets should not be cloned. Two objects sharing one connection is a bug, and copying a connection is usually impossible.
- The object graph has cycles or hidden state. Copying it correctly is harder than rebuilding it.
- Every field must be kept in sync. A hand-written
clone()has to be updated each time a field is added. Forget one and clones silently lose data. A test that clones and compares helps.
What interviewers ask
- "Shallow versus deep copy?" Shallow shares nested references, deep copies them. Show the array example.
- "How do you deep clone in JavaScript?"
structuredClone, and say why notJSON.parse(JSON.stringify(x)). - "How does Prototype relate to JavaScript itself?" Objects inherit through prototypes, and
Object.createis the pattern built into the language. - "When is cloning better than a constructor?" When construction is expensive, or when the state was assembled step by step and you want to reuse it.
- "What breaks a clone?" Nested references, cycles, and fields added to the class but not to
clone().
The mistake to avoid
A Prototype's clone() that copies a reference instead of the value behind it. It is the same shallow-copy bug as the Builder one, one level earlier: whatever a clone shares with its template by reference, every future clone shares too. After writing clone(), mutate a nested field on the copy and check that the original did not change.
Prototype is one naming device among several an object-oriented language gives you for controlling how
newgets called. The mechanism above is the same, syntax aside, whether the language is JavaScript, Python or Java.
Now practice it
1 challenge · 2 quizzesReading this page is the cheap half. These are the exercises that make you use it.
- PrototypeProClone a configured instance and prove the copy is independent of its template — the exact shallow-copy trap this page walks through.ChallengeSolve · EasyChallengeSolve · Easy
- Design Patterns · Creational PatternsTen quick questions across Singleton, Factory Method, Abstract Factory, Builder and Prototype, to check the mental model before this one.QuizTake · EasyQuizTake · Easy
- Design Patterns · Creational Patterns — Series 2ProA second, tougher pass on the same creational family, for once the first set feels easy.QuizTake · EasyQuizTake · Easy
Read next
Builder pattern
Replacing a constructor drowning in optional parameters, the extra code it costs for objects simple enough not to need it, and build() handing back the same object twice.
6 min
Abstract Factory pattern
Guaranteeing a whole family of related objects ships together, the extra interface every product carries, and the mismatch when a factory returns the wrong family.
7 min