Prototype: Shallow vs Deep Copy
The Problem It Solves
A game spawns thousands of enemies sharing 95% configuration (model, stats, AI tree) differing in position. Reconstructing each from scratch means re-parsing configs, rebuilding nested structures — expensive repeated work for nearly-identical objects. Prototype copies an existing, fully-configured instance instead of building from blueprints. The entire difficulty of the pattern lives in one question: what exactly does “copy” mean when fields point to other objects?
Shallow vs Deep
ORIGINAL SHALLOW copy.clone() DEEP copy
enemy ┌─ pos ────────┐ clone ┌─ pos ──┐ clone ┌─ pos(new)┐
│ │ ▲ │ ▲
├─ weapon ◄────┼─────────────┼────────────────┼────────────┤
│ (shared!) │ clone.weapon IS original.weapon both have OWN weapon
└─ tags[...] ──┼─────────────┘
│ mutating shared.weapon via EITHER
▼ reference corrupts BOTH objects
- Shallow: top-level fields duplicated; referenced objects shared. Cheap; correct only for immutable or intentionally-shared parts.
- Deep: entire object graph recursively copied. Safe independence; costs time proportional to graph size.
class Enemy {
private final Position pos; // mutable value object
private final Weapon weapon; // mutable aggregate
private final List<String> tags;
// DEEP COPY CONSTRUCTOR — the recommended technique
Enemy(Enemy other) {
this.pos = new Position(other.pos);
this.weapon = new Weapon(other.weapon);
this.tags = List.copyOf(other.tags); // defensive immutable copy
}
Enemy deepCopy() { return new Enemy(this); }
}
Why Copy Constructors Beat clone()
Cloneable is a famously broken design (Effective Java Item 13):
| Issue | Consequence |
|---|---|
Object.clone() is native shallow copy | Deep needs risky override surgery |
Cloneable has no methods | Pure marker; clone() sits on Object anyway |
| Final fields | clone() assigns them natively — can’t re-set deep-copied values in the subclass |
| Contracts (“no constructor called”) | Invariants enforced in constructors get bypassed |
| Exceptions | CloneNotSupportedException checked noise |
Copy constructors/deepCopy() methods are ordinary code — type-safe, final-field-compatible, invariant-respecting.
Other Production Techniques
- Serialization round-trip: serialize to bytes, deserialize → deep copy for free; slow but handles arbitrary graphs (used in snapshotting state stores).
- Immutable prototypes: if the prototype is fully immutable, shallow “copies” are just shared references — the cheapest possible cloning; combine with copy-on-write for mutation.
- Registry pattern:
prototypes.get("orc").deepCopy()— config-loaded archetypes cloned per spawn.
Real-World Sightings
- Game engines: entity templates cloned per spawn.
Object.clone()on arrays: shallow but fine — elements are primitives/immutable in most uses.- Spring’s prototype bean scope: container hands a fresh instance per request — same idea at framework scale (though it constructs rather than copies).
Cost Model (illustrative)
Copying a 1 KB object graph with 10 nested mutables ≈ microseconds vs milliseconds to rebuild from config parse + network fetch — the win evaporates if graphs are huge; then build-once-share-immutables beats deep-copying.
Interview Framing
- The scoring moment is the diagram question: “your clone shares
weapon— what breaks?” Answer with the aliasing-mutation story. - Recommending copy constructors over
Cloneablewith reasons is the experienced signal.
Premium Content
Unlock Prototype and all premium lessons with a subscription.
From ₹199.99/year — See plans