Prototype

June 17, 2026 in patterns 5 minutes

A intermediate-level guide to Prototype: before-and-after java code and diagrams for a CS student.

The Cost of Complex Instantiation

When you first learn to create objects using the new keyword (the instantiation process), it seems simple. You call a constructor, pass in some values, and you have an object. However, as systems grow, creating an object can become “heavy.” This happens when an object requires complex setup logic, many parameters, or expensive data fetching before it is ready to use.

Consider a scenario where we need to create multiple levels for a game. Each level has a name, a list of enemy types, and a specific difficulty setting. If every time we want a new version of a level, we have to manually pass in all these details again, we risk errors and repetitive code.

import java.util.*;

class GameLevel {
    private final String name;
    private final List<String> enemyTypes;
    private final int difficulty;

    // Creating a new level requires many parameters and complex setup logic
    public GameLevel(String name, List<String> enemyTypes, int difficulty) {
        this.name = name;
        this.enemyTypes = enemyTypes;
        this.difficulty = difficulty;
    }


    @Override
    public String toString() {
        return "Level: " + name + ", Enemies: " + enemyTypes + ", Difficulty: " + difficulty;
    }
}

The problem here is that the client (the part of the program using this class) must know exactly how to construct a GameLevel from scratch every single time. If the construction process becomes more complex—perhaps involving database calls or heavy computations—the client is forced to manage all that complexity.

  classDiagram
    class GameLevel {
        -String name
        -List enemyTypes
        -int difficulty
        +GameLevel(name, enemyTypes, difficulty)
    }
    class Client {
        +createLevels()
    }
    Client ..> GameLevel : creates via constructor

The Intuition: A Photocopy Machine

Think of a complex document you spent hours typing and formatting. If you need five copies of it to distribute to teammates, you do not re-type the entire document five times. Instead, you put the original in a photocopy machine and press “copy.”

The Prototype pattern applies this exact logic to software. Rather than using new to build an object from the ground up, you take an existing “prototype” object and ask it to produce a duplicate of itself.

Implementing the Prototype Pattern

To implement this, we move the responsibility of creation away from the client and into the object itself. We define a common interface that specifies how an object should copy itself.

import java.util.*;

interface Prototype<T> {
    T copy();
}

class GameLevel implements Prototype<GameLevel> {
    private String name;
    private List<String> enemyTypes;
    private int difficulty;

    public GameLevel(String name, List<String> enemyTypes, int difficulty) {
        this.name = name;
        this.enemyTypes = new ArrayList<>(enemyTypes);
        this.difficulty = difficulty;
    }

    @Override
    public GameLevel copy() {
        // Deep copy: we create a new list so the clone doesn't share enemy lists with the original
        return new GameLevel(this.name, this.enemyTypes, this.difficulty);
    }

    public void setName(String name) { this.name = name; }


    @Override
    public String toString() {
        return "Level: " + name + ", Enemies: " + enemyTypes + ", Difficulty: " + difficulty;
    }
}

By implementing the Prototype interface, GameLevel now provides a copy() method. The client no longer needs to know how to build a level; it only needs to know which existing level it wants to duplicate.

  sequenceDiagram
    participant C as Client
    participant P as GameLevel (Prototype)
    C ->> P: copy()
    P ->> P: new GameLevel(...)
    P -->> C: returns new instance

The Deep vs. Shallow Divide

A critical concept when duplicating objects is the difference between a “shallow copy” and a “deep copy.” This is where many developers encounter bugs.

In a shallow copy, only the top-level fields are copied. If one of those fields is a reference (a pointer to another object, like an ArrayList), both the original and the copy will point to the exact same list in memory. Changing the enemies in the clone would accidentally change the enemies in the original.

In a deep copy, we also duplicate any objects held by the original. In our solution, notice that when we create the new GameLevel, we pass a new instance of an ArrayList containing the existing enemy types. This ensures the two levels are truly independent.

  flowchart TD
    subgraph ShallowCopy [Shallow Copy: Shared Reference]
        direction LR
        S1[Original Level] --> L[Shared List: Enemy Types]
        S2[Cloned Level] --> L
    end
    subgraph DeepCopy [Deep Copy: Independent Data]
        direction LR
        D1[Original Level] --> L1[List A]
        D2[Cloned Level] --> L2[List B]
    end

When to Use It, and When Not To

The Prototype pattern is a creational design pattern. Unlike the Factory Method, which focuses on how classes are structured to delegate creation to subclasses, Prototype focuses on duplicating existing instances.

Use Prototype when:

  • Object creation involves heavy computation or complex configuration that is expensive to repeat.
  • You want to hide the complexity of creating different variations of an object from the client.
  • You need to keep a “master copy” of an object and create many similar versions of it.

Avoid Prototype when:

  • The objects are simple “value types” (like integers or basic strings) where new is already extremely efficient.
  • Implementing deep copies becomes too complex due to highly nested, circular, or massive object graphs.

Takeaways

  • Use Prototype to avoid expensive or complex construction logic by cloning existing instances.
  • A shallow copy only duplicates references; a deep copy duplicates the actual data inside those references.
  • The pattern simplifies client code by hiding the details of how an object is composed.

Usage

Using copy() to duplicate a heavy object and modify only specific parts.

GameLevel base = new GameLevel("Forest", List.of("Orc", "Goblin"), 5);
GameLevel clone = base.copy();
clone.setName("Dark Forest");

System.out.println(base);   // Level: Forest, Enemies: [Orc, Goblin], Difficulty: 5
System.out.println(clone);  // Level: Dark Forest, Enemies: [Orc, Goblin], Difficulty: 5