CMD Guide
HomeOO & Low-Level DesignCreational

Prototype Pattern

The Prototype pattern builds a new object not by running its constructor but by byte-copying an existing, already-configured instance and then tweaking the copy — so the cost and complexity of constructing the original (parsing config, hitting a DB, deep object graphs) is paid once and amortised across every clone. In Java the copy is done by Object.clone(), which performs a field-by-field bit copy of the source object: that is the whole mechanism, and it is also the whole danger.

The mechanism: what super.clone() actually copies

Object.clone() allocates a new object of the same runtime class and copies each field's bits from source to copy. For a primitive (int, boolean) or an immutable reference (String), copying the bits is enough — there is nothing to share. But a field that holds a reference to a mutable object (an ArrayList, a Date, a nested mutable config) gets its pointer copied, not the object it points to. The clone and the original now hold the same reference — they are aliased. Mutating through one is visible through the other. This split is the named distinction the textbooks call shallow copy (copy the pointer) versus deep copy (copy the pointee too).

The original version of this page only had String and primitive fields, so its shallow clone was trivially safe and the “deep-copy bug” it warned about could never actually occur. Below we give the prototype a real mutable field — a list of accessories — so the hazard is concrete.

The naive (buggy) shallow clone

abstract class Car implements Cloneable {
  protected String model;
  protected String color;
  protected List<String> accessories = new ArrayList<>(); // MUTABLE field

  public abstract void customize(String color, String accessory);

  // BUG: super.clone() copies the *reference* to accessories, not the list.
  public Car shallowClone() {
    try {
      return (Car) super.clone();        // clone.accessories == this.accessories
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(e);
    }
  }
}

Why the naive version is wrong: after super.clone(), the clone's accessories field points at the exact same ArrayList as the prototype's. Add a sunroof to one clone and every other clone — and the prototype itself — sprouts a sunroof, because there is only one list. The bug is silent: nothing throws, the corruption just leaks across objects that were supposed to be independent.

diagram
diagram

The correct deep clone (plus a Prototype Registry)

The fix: after super.clone() gives you the bit-copy, replace each mutable field with a fresh copy of its own. A registry then keeps named, pre-built prototypes and hands out a fresh clone on demand, so the client never names a concrete class.

abstract class Car implements Cloneable {
  protected String model;
  protected String color;
  protected List<String> accessories = new ArrayList<>();

  public abstract void customize(String color, String accessory);

  @Override
  public Car clone() {
    try {
      Car c = (Car) super.clone();                 // 1. bit-copy all fields
      c.accessories = new ArrayList<>(this.accessories); // 2. deep-copy the mutable one
      return c;                                     //    primitives/String need no fix
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(e);                  // can't happen: we implement Cloneable
    }
  }
}

class BasicCar extends Car {
  public BasicCar() { model = "Basic"; color = "White"; accessories.add("ABS"); }
  @Override public void customize(String color, String accessory) {
    this.color = color;
    this.accessories.add(accessory);
  }
}

// Prototype Registry — client asks by key, gets an independent clone
class PrototypeRegistry {
  private final Map<String, Car> protos = new HashMap<>();
  public void register(String key, Car proto) { protos.put(key, proto); }
  public Car create(String key)               { return protos.get(key).clone(); }
}

Go has no clone() and no inheritance, so the idiom is an explicit Clone() method on an interface — which makes the deep-copy step impossible to forget because you write every field:

type Car interface {
	Clone() Car
	Customize(color, accessory string)
}

type BasicCar struct {
	Model, Color string
	Accessories  []string
}

func (b *BasicCar) Clone() Car {
	cp := make([]string, len(b.Accessories))
	copy(cp, b.Accessories)               // deep-copy the slice; sharing the
	return &BasicCar{b.Model, b.Color, cp} // backing array would be the same bug
}

func (b *BasicCar) Customize(color, accessory string) {
	b.Color = color
	b.Accessories = append(b.Accessories, accessory)
}

Worked trace: same calls, two outcomes

Prototype starts as Basic/White with accessories = [ABS]. We make two clones, then call a.customize("Red", "Sunroof") on the first. The identity hash of the list (its object id) is printed so you can see whether lists are shared. These are the real values printed by the compiled program:

StepActionShallow clone resultDeep clone result
1two clones made from prototypea, b both point at list @705927765c → @366712642, d → @1829164700
2customize("Red","Sunroof") on first cloneadds to the one shared listadds only to c's own list
3inspect first cloneBasic/Red [ABS, Sunroof]Basic/Red [ABS, Sunroof]
4inspect untouched second cloneBasic/White [ABS, Sunroof]Basic/White [ABS]
5inspect the prototype itselfBasic/White [ABS, Sunroof] ❌ pollutedBasic/White [ABS] ✓ pristine

In the shallow run, customizing one clone silently mutated a second clone and corrupted the prototype, because all three share the single list @705927765. In the deep run every object has its own list id and stays independent. Note that model and color never leak in either run — they are String, so a shallow copy of them is already safe. Only the mutable field needs the deep copy.

Pitfalls

When to use it / when NOT to

Reach for Prototype when: (1) constructing a fresh object is genuinely expensive or complex — it loads from disk/DB, runs heavy computation, or assembles a deep graph — and you need many near-identical instances (e.g. building an object from scratch parses a large config and takes ~50 ms, while clone() + a field tweak is ~0.5 ms; the 100× gap is the whole reason to keep a pre-built prototype around); (2) the concrete classes are decided at runtime and you want to add/remove producible types by registering instances rather than writing factory subclasses; (3) you have a fully-configured object and want a few variants without re-specifying the whole config.

Trade-offs versus the alternatives

Concrete decision: a level editor lets designers drag pre-tuned enemy types (each with HP, an AI script, a loot table) onto a map. The set of types grows as designers invent them, and each is an expensive-to-configure object. That is exactly runtime type selection + costly construction → register one tuned instance per enemy type and clone on drop. By contrast, an HTTP request object built fresh per call from a handful of headers wants a Builder or copy constructor, not a prototype registry.

Takeaways


Sources: Gamma, Helm, Johnson & Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Prototype, pp. 117–126); Joshua Bloch, Effective Java 3rd ed., Item 13 (“Override clone judiciously”); Refactoring.Guru, Prototype. Java and Go examples compiled and run for this guide; printed object-identity hashes are from the actual program output. Re-authored and deepened for this guide — added a mutable field so the shallow-vs-deep aliasing bug is demonstrated end-to-end, a corrected deep clone(), a Prototype Registry, a traced before/after example, and a selection & trade-offs section.

🤖 Don't fully get this? Learn it with Claude

Stuck on Prototype Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Prototype Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Prototype Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Prototype Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Prototype Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Prototype Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes