Skip to main content

Command Palette

Search for a command to run...

Prototype Design Pattern

Updated
5 min read
Prototype Design Pattern

While developing applications, we often create multiple objects that are very similar to each other. The only difference is that a few properties change while the rest of the object remains the same.

In such situations, creating a new object from scratch every time may not be efficient, especially when object initialization is expensive or contains complex setup logic.

For example, imagine a Document object that requires:

  • Loading default settings

  • Applying themes

  • Setting permissions

  • Initializing metadata

If we need hundreds of similar documents, repeatedly running the same initialization logic becomes unnecessary.

This is where the Prototype Design Pattern becomes useful.

What is the Prototype Design Pattern?

Prototype is a creational design pattern that creates new objects by copying an existing object, known as a prototype.

Instead of creating a new object using constructors, we create new objects by cloning an already existing object.

The core idea is:

Create by copying rather than constructing.

This helps avoid costly re-initialization and allows us to quickly create similar objects.

              Prototype
                 │
      ┌──────┴──────┐
      │                     │
    Clone                   Clone
      │                     │
   Object A                 Object B

Shallow Copy vs Deep Copy

When implementing the Prototype Pattern, there are generally two ways to copy an object:

  1. Shallow Copy

  2. Deep Copy

Understanding the difference between these two approaches is extremely important.

Shallow Copy

In a shallow copy, primitive values are copied, but references are not duplicated.

Suppose an object contains a list:

Document document = new Document(
    "Prototype Pattern",
    "Content",
    users
);

When the object is cloned, the new object receives the same reference to the list.

This means both objects are pointing to the same list in memory.

If one object modifies the list:

clone.getUsers().add("John");

the change will be visible in the original object as well.

This behavior can introduce unexpected side effects if we are not careful.

Example

class Document {

    private String title;
    private String content;
    private List<String> users;

    public Document(
            String title,
            String content,
            List<String> users) {
        this.title = title;
        this.content = content;
        this.users = users;
    }

    // Copy Constructor
    public Document(Document other) {
        this.title = other.title;
        this.content = other.content;
        this.users = other.users; // Same reference
    }
    
    public Document clone() {
        return new Document(this);
    }
}

Notice that the users list is not copied. Both objects share the same reference.

Deep Copy

Deep copy behaves differently.

Instead of copying references, it creates completely new objects for nested references.

Original ----> Users List A

Clone -------> Users List B

Now the cloned object has its own independent list.

If the clone modifies its list, the original object remains unaffected.

Example

class Document {

    private String title;
    private String content;
    private List<String> users;

    public Document(
            String title,
            String content,
            List<String> users) {
        this.title = title;
        this.content = content;
        this.users = users;
    }

    // Deep Copy Constructor
    public Document(Document other) {
        this.title = other.title;
        this.content = other.content;
        this.users = new ArrayList<>(other.users);
    }

    @override
    public Document clone() {
       return copyData(new Document());
    }
    
    private copyData(Document otherObject) {
        super.copyData(otherObject);
        otherObject.users = new ArrayList<>(other.users);
        otherObject.title = this.title;
        otherObject.content = this.content;
        return otherObject;
    }
}

Here we create a completely new list:

this.users = new ArrayList<>(other.users);

As a result, changes made to the cloned object do not affect the original object.

Cloneable vs Copy Constructor

Java provides the Cloneable interface and the Object.clone() method.

However, many developers avoid using them in modern Java because:

  • Cloneable does not define a clone method.

  • Object.clone() performs shallow copying by default.

  • It throws CloneNotSupportedException.

  • Deep-copy implementations become difficult to maintain.

For these reasons, copy constructors are generally preferred.

The intent is clearer and the implementation is easier to control.

Advantages

Faster Object Creation

If object initialization is expensive, cloning can be much faster than creating everything from scratch.

Reduces Repeated Initialization

Complex setup logic only needs to be written once.

Cleaner Code

Instead of passing large numbers of constructor parameters repeatedly, we can clone an existing object and modify only the required fields.

Drawbacks

Deep Copy Can Be Expensive

For large object graphs, deep copying may consume additional memory and CPU.

Shallow Copy Bugs

Sharing references accidentally can lead to unexpected behavior.

Additional Complexity

Implementing cloning logic correctly becomes harder as objects become more complex.

Prototype vs Factory Pattern

A common question is how Prototype differs from Factory.

Factory Pattern

Creates objects using constructors.

User user = UserFactory.createAdmin();

Prototype Pattern

Creates objects by cloning existing instances.

User user = adminPrototype.cloneUser();

A simple rule of thumb:

  • Use Factory when creation logic is complex.

  • Use Prototype when similar objects need to be created repeatedly.

A simple way to remember this pattern is:

If creating an object is expensive and the new object looks very similar to an existing one, consider using the Prototype Design Pattern.