Creational

Prototype

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying that prototype.

Beginner Pattern #05 of 23

What is the Prototype Pattern?

The Prototype pattern lets you create new objects by cloning an existing instance (the prototype) rather than building from scratch via constructors. The object itself knows how to copy itself — delegating the cloning logic inside the class rather than exposing it externally.

This is especially powerful when object creation is expensive (e.g., DB queries, network calls, heavy computation) and you just need a slightly different version of an existing object.

Real-world analogy: Biological cell division — a cell copies itself to create a new identical cell, rather than assembling a new cell from raw atoms each time. The prototype is the original cell; the clone is the copy.

The Problem It Solves

Suppose you have a complex object loaded from a database — parsed, validated, and populated with many fields. You need a near-identical copy but with a few tweaks. Constructing a new one from scratch is expensive and wasteful. And you can't just copy fields externally because some may be private.

Furthermore, you may not even know the exact class of the object at runtime — just its interface. You can't call new ConcreteClass() when you only have an interface reference.

Participants

Prototype (interface)
Declares the clone() method. In Java, this is the Cloneable interface (or a custom one).
ConcretePrototype
Implements clone() to return a deep or shallow copy of itself. Contains all the copying logic internally.
Client
Creates a new object by asking a prototype to clone itself. Never calls new on the concrete class directly.
Prototype Registry (optional)
A store of pre-built prototypes keyed by name/type. Client fetches prototype from registry and clones it, avoiding any coupling to concrete classes.

Visual Flow Diagram

Client calls clone() ProtoRegistry Map<key, prototype> getPrototype(key) «interface» Prototype clone() CirclePrototype radius: int color: String clone() { return copy; } RectPrototype width, height: int color: String clone() { return copy; } Cloned Object independent copy stores

Java Code Example

Java
// Prototype interface
public abstract class Shape implements Cloneable {
    protected String color;
    protected int x, y;

    public Shape(Shape target) {
        // Copy constructor — used by clone()
        if (target != null) {
            this.color = target.color;
            this.x = target.x;
            this.y = target.y;
        }
    }

    public abstract Shape clone();

    public boolean equals(Object o) {
        if (!(o instanceof Shape)) return false;
        Shape s = (Shape) o;
        return s.color.equals(color) && s.x == x && s.y == y;
    }
}

// Concrete Prototype 1
public class Circle extends Shape {
    public int radius;

    public Circle(Circle target) {
        super(target);
        if (target != null) this.radius = target.radius;
    }

    public Circle clone() { return new Circle(this); }
}

// Concrete Prototype 2
public class Rectangle extends Shape {
    public int width, height;

    public Rectangle(Rectangle target) {
        super(target);
        if (target != null) {
            this.width  = target.width;
            this.height = target.height;
        }
    }

    public Rectangle clone() { return new Rectangle(this); }
}

// Prototype Registry — stores pre-built prototypes by key
public class ShapeRegistry {
    private static Map<String, Shape> cache = new HashMap<>();

    static {
        Circle c = new Circle(null);
        c.color = "red"; c.radius = 10;
        cache.put("red-circle", c);

        Rectangle r = new Rectangle(null);
        r.color = "blue"; r.width = 20; r.height = 30;
        cache.put("blue-rect", r);
    }

    public static Shape getShape(String key) {
        return cache.get(key).clone(); // always return a clone
    }
}

// Client usage
public class Main {
    public static void main(String[] args) {
        Shape c1 = ShapeRegistry.getShape("red-circle");
        Shape c2 = ShapeRegistry.getShape("red-circle");

        System.out.println(c1 == c2);         // false — different objects
        System.out.println(c1.equals(c2));    // true  — same field values

        // Mutate the clone freely — original is unaffected
        ((Circle) c1).radius = 99;
        System.out.println(((Circle) c2).radius); // still 10
    }
}

// ⚠ Shallow vs Deep Clone
// Shallow: copies primitive fields, shares object references (default Object.clone())
// Deep:    recursively copies nested objects — must implement manually
public DeepObject clone() {
    DeepObject copy = (DeepObject) super.clone(); // shallow
    copy.nestedList = new ArrayList<>(this.nestedList); // deep copy list
    return copy;
}

When to Use / Avoid

✓ Use When

  • Object creation is expensive (DB lookup, network call, heavy init)
  • You need many similar objects that differ only slightly
  • You don't know the exact class of the object to create at runtime
  • You want to avoid subclassing just to configure objects differently

✕ Avoid When

  • Objects have circular references — deep cloning becomes very tricky
  • Construction is cheap — cloning adds unnecessary complexity
  • All objects of a type are always unique — no benefit to prototype sharing

Real-World Examples

Pros & Cons

Pros

  • Faster than constructing from scratch when initialization is heavy
  • Clone at runtime without knowing the concrete class
  • Alternative to subclassing for object variation
  • Prototype Registry enables named, reusable object templates

Cons

  • Deep cloning with circular references is complex and error-prone
  • Each subclass must implement clone() — easy to forget
  • Java's Cloneable interface is considered poorly designed
  • Shallow copies can cause subtle bugs with shared mutable state

How Prototype Can Be Broken

⚠ Attack Vectors

  • Shallow copy shared mutation: Default Object.clone() is shallow — cloned object shares references to nested mutable objects. Mutating the clone's nested object changes the original too
  • Circular references: Object A references B, which references A — a naive deep clone loops infinitely or causes a StackOverflowError
  • Cloning a Singleton: If a Singleton implements Cloneable, cloning it produces a second instance and breaks the Singleton guarantee
  • Forgetting to override clone(): Subclass adds new fields but doesn't update clone() — new fields are lost silently in the copy
  • Using stale registry prototypes: Prototype registry holds a cached object whose state has drifted; clones inherit outdated state

✓ Prevention

  • Always implement deep clone for mutable fields: Explicitly clone every mutable nested object — do not rely on the default shallow super.clone() when nested objects exist
  • Circular reference fix: Track visited objects in a Map<Original, Clone> during cloning — if already cloned, return the existing copy instead of recursing
  • Singleton cloning fix: Override clone() in the Singleton to throw CloneNotSupportedException, or do not implement Cloneable at all
  • Copy constructor over Cloneable: Prefer explicit copy constructors (new Circle(original)) over Object.clone() — clearer, safer, and avoids Java's broken Cloneable design
  • Serialization-based deep clone: Serialize to byte array then deserialize — automatically handles deep copy but is slower; use only when object graph is complex
Java — Break & Fix
// ❌ BREAKING — shallow clone shares mutable nested list
public class UserProfile implements Cloneable {
    public List<String> roles = new ArrayList<>();

    public UserProfile clone() {
        return (UserProfile) super.clone(); // shallow! roles list is shared
    }
}
UserProfile original = new UserProfile();
original.roles.add("admin");
UserProfile clone = original.clone();
clone.roles.add("superuser"); // also modifies original.roles!

// ✅ FIX — deep clone the mutable field
public UserProfile clone() {
    UserProfile copy = (UserProfile) super.clone();
    copy.roles = new ArrayList<>(this.roles); // independent copy
    return copy;
}

// ❌ BREAKING — circular reference causes infinite recursion
// Node A has reference to B; B has reference back to A
// Naive deep clone: clone A → clone B → clone A → ... StackOverflow

// ✅ FIX — visited map breaks the cycle
public Node deepClone(Map<Node, Node> visited) {
    if (visited.containsKey(this)) return visited.get(this); // reuse
    Node copy = new Node(this.value);
    visited.put(this, copy);
    copy.next = (next != null) ? next.deepClone(visited) : null;
    return copy;
}

// ✅ Serialization-based deep clone (handles complex graphs)
public static <T extends Serializable> T deepClone(T obj) {
    try {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        new ObjectOutputStream(bos).writeObject(obj);
        return (T) new ObjectInputStream(
            new ByteArrayInputStream(bos.toByteArray())).readObject();
    } catch (Exception e) { throw new RuntimeException(e); }
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Create new objects by cloning an existing prototype rather than instantiating from scratch — delegates creation to the object itself via clone().
  2. How: Implement Cloneable (or a custom interface). Use a copy constructor for clean deep cloning. Optionally use a Prototype Registry (Map) to store named templates and always return clones.
  3. Critical interview point: Know the difference between shallow clone (copies primitives, shares references) and deep clone (fully independent copy). Deep cloning circular references requires special care — serialization trick is a common workaround.