While working on modern software systems with complex objects, these objects sometimes contain large amounts of configuration data, templates, or default values. Creating such objects repeatedly from scratch could be inefficient and also becomes error prone.
The prototype pattern allows us to create new objects by cloning existing ones instead of constructing them from scratch. This can simplify object creation, improve performance, and reduce code complexity.
What is Prototype Pattern?
The Prototype Pattern is a creational design pattern that allows objects to be copied or cloned rather than created using constructors. Rather than instantiating a new object, manually setting all the properties, the system duplicates an existing object(prototype) and modifies it as needed.
Why do we need the Prototype Pattern?
In many real-world system, object creation is not always simple. Some objects might require complex initialization logic like Database lookups, Configuration loading, and Resource allocation, setting many default properties, when such objects are created frequently, constructing them from scratch might not be efficient.
Understanding the Prototype Pattern Structure
The prototype pattern typically consists of three components.
Prototype interface — which defines the cloning operation.
Concrete Prototype — which implements the cloning functionality.
Client — which creates new objects by cloning prototypes.
Shallow Copy vs Deep Copy
When implementing the Prototype Pattern, it is important to understand the difference between shallow and deep copying.
Shallow Copy — This copies the object but keep references to the nested objects. we use MemberwiseClone() for this.
Deep Copy — It creates completely independent copies of nested objects as well. It required when object contains complex reference types like Lists, Child objects, Collections etc.
Let’s consider an example of the billing system. Where system generates invoices. Every invoice typically contains similar structure with InvoiceNumber, CustomerName, and BillingAddress.
First let’s define a prototype interface IPrototype<T>
public interface IPrototype<T>
{
T ShallowCopy();
T DeepCopy();
}
Let’s create a supporting class for Address
public class Address
{
public string City { get; set; }
public string Country { get; set; }
}
Create a Concrete prototype i.e. invoice class that implements the prototype interface and provides the cloning behavior.
public class Invoice : IPrototype<Invoice>
{
public string InvoiceNumber { get; set; }
public string CustomerName { get; set; }
public Address BillingAddress { get; set; }
// Shallow Copy
public Invoice ShallowCopy()
{
return (Invoice)this.MemberwiseClone();
}
// Deep Copy
public Invoice DeepCopy()
{
var clone = (Invoice)this.MemberwiseClone();
clone.BillingAddress = new Address
{
City = this.BillingAddress.City,
Country = this.BillingAddress.Country
};
return clone;
}
public void Print()
{
Console.WriteLine($"Invoice: {InvoiceNumber}");
Console.WriteLine($"Customer: {CustomerName}");
Console.WriteLine($"City: {BillingAddress.City}");
Console.WriteLine($"Country: {BillingAddress.Country}");
}
}
Now in program.cs use the prototype to create a new object by cloning it.
var invoiceTemplate = new Invoice
{
InvoiceNumber = "INV-1000",
CustomerName = "Nabaraj Ghimire",
BillingAddress = new Address
{
City = "Kathmandu",
Country = "Nepal"
}
};
// Shallow Copy Example
var shallowInvoice = invoiceTemplate.ShallowCopy();
shallowInvoice.InvoiceNumber = "INV-1001";
shallowInvoice.BillingAddress.City = "Pokhara";
Console.WriteLine("Original Object:");
invoiceTemplate.Print();
Console.WriteLine("\n");
Console.WriteLine("Shallow Copy Result:");
shallowInvoice.Print();
Console.WriteLine("\n");
// Deep Copy Example
var deepInvoice = invoiceTemplate.DeepCopy();
deepInvoice.InvoiceNumber = "INV-1002";
deepInvoice.BillingAddress.City = "Lalitpur";
Console.WriteLine("Deep Copy Result:");
deepInvoice.Print();
The output will be like this.
![]()
If we look closely at the behavior of both cloning methods, we can see an important difference.
In the shallow copy, the cloned object shares the same Address reference as the original object. Because of this, when the city value is changed to “Pokhara” in the cloned object, the change is also reflected in the original object. This happens because both objects are pointing to the same Address instance in memory.
In contrast, the deep copy creates a completely new Address instance for the cloned object. As a result, any changes made to the address in the cloned object will not affect the original object. Each object maintains its own independent copy of the Address.
Conclusion
The Prototype pattern is a simple yet powerful design pattern that allows objects to be created by cloning existing instances. With prototype we can reduce repetitive initialization, improve performance and simplify object creation logic. Real world implementation could be billing platforms, template engines, game entity creation etc. It is a good way to reuse object configuration while keeping the system flexible and scalable.