Skip to main content

Prototype Pattern

Category: Creational
Intent: Create new objects by cloning an existing instance (prototype) rather than constructing from scratch.


Overviewโ€‹

The Prototype pattern copies existing objects without making the code dependent on their classes. Instead of calling new and re-running expensive initialization, you duplicate an already-configured object and tweak it as needed.

Key characteristics:

  • Objects are created by cloning a prototype instance
  • Avoids the cost of re-creating objects with expensive initialization
  • New types can be introduced at runtime by cloning different prototypes

๐Ÿ‘ถ Explain Like I'm 5โ€‹

Imagine you draw a beautiful picture of a house. Now your friend wants the same picture but with a blue door instead of red. Instead of drawing the entire house again from scratch, you just photocopy your picture and color the door blue. Much faster!

The Prototype pattern is that photocopier. Instead of building a new object from nothing (which might take a lot of time โ€” reading files, querying databases), you copy an existing object and change just what you need.


โ“ Problem & Solutionโ€‹

The Problem: Imagine you have an object, and you want to create an exact copy of it. How would you do it? Normally, you create a new object of the same class, then go through all the fields of the original object and copy their values over. However, not all objects can be copied that way since some fields may be private and not visible from outside the object. Furthermore, doing this makes your code highly dependent on the concrete classes of the objects you copy. Sometimes you only know the interface the object follows, but not its concrete class (e.g., when the object is passed to a method as a parameter).

The Solution: The Prototype pattern delegates the cloning process to the actual objects that are being cloned. The pattern declares a common interface for all objects that support cloning, usually containing just a single clone() method. This lets you clone an object without coupling your code to the specific class of that object.


๐ŸŒ Real-World Analogyโ€‹

A great analogy is mitotic cell division in biology. After a cell splits, two completely identical cells form. The original cell acts as a prototype and takes an active role in creating the exact copy. Another everyday analogy is formatting a document. Instead of creating a new document from scratch and manually setting up the margins, fonts, and headers, you simply duplicate an existing, well-formatted document (the prototype) and just change the text contents.


๐Ÿ—๏ธ Structureโ€‹


When to Useโ€‹

โœ… Use this when:

  • Object creation is expensive (heavy initialization, DB calls, file I/O, network requests) and you can avoid the cost by cloning.
  • You need many similar objects with slight variations (e.g., game entities, test fixtures, document templates).
  • The exact type of object isn't known at compile time โ€” you only have a reference to an interface.
  • You want to avoid building complex factory hierarchies just to create different object configurations.
  • Configuring a prototype once and cloning it is simpler than re-specifying all parameters each time.

โŒ Don't use this when:

  • Object creation is cheap โ€” new MyObject() with simple field assignments doesn't justify the cloning complexity.
  • Objects contain circular references or complex resource handles (DB connections, file locks) that can't be trivially cloned.
  • The object is immutable โ€” there's no need to clone immutable objects; just share the reference.
  • You're in a Spring DI environment and can use Spring's @Scope("prototype") to create new instances automatically.

๐Ÿ” Quick Decision Checklist:

  1. Is creating the object slow (>1ms, DB call, parsing)? โ†’ Yes = Prototype saves time.
  2. Do you need many copies with small differences? โ†’ Yes = Clone + tweak.
  3. Does the object have mutable nested objects? โ†’ Be careful โ€” you need deep clone.
  4. Can you just use new? โ†’ Yes = Don't over-engineer with Prototype.

How It Worksโ€‹

Shallow Clone vs Deep Cloneโ€‹

Understanding the difference is critical:

Original object: [ name: "A", list: โ†’ [1, 2, 3] ]
โ†‘
Shallow clone: [ name: "A", list: โ”€โ”˜ ] โ† shares the same list!

Deep clone: [ name: "A", list: โ†’ [1, 2, 3] ] โ† independent copy
TypeBehaviorRisk
ShallowCopies field values directly; reference fields point to the same objectsModifications to referenced objects affect both original and clone
DeepRecursively copies all referenced objectsSafer but more expensive and complex to implement
public abstract class Shape {
private String color;
private int x, y;

public Shape() {}

// Copy constructor
protected Shape(Shape source) {
this.color = source.color;
this.x = source.x;
this.y = source.y;
}

public abstract Shape clone();

public void setColor(String color) { this.color = color; }
public void moveTo(int x, int y) { this.x = x; this.y = y; }

@Override
public String toString() {
return getClass().getSimpleName() + "{color='" + color + "', x=" + x + ", y=" + y + "}";
}
}

public class Circle extends Shape {
private int radius;

public Circle(int radius) { this.radius = radius; }

// Copy constructor โ€” deep copies all fields
private Circle(Circle source) {
super(source);
this.radius = source.radius;
}

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

@Override
public String toString() {
return "Circle{radius=" + radius + ", " + super.toString() + "}";
}
}

public class Rectangle extends Shape {
private int width, height;

public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}

private Rectangle(Rectangle source) {
super(source);
this.width = source.width;
this.height = source.height;
}

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

// Usage
Circle original = new Circle(10);
original.setColor("Red");
original.moveTo(5, 5);

Circle copy = original.clone(); // independent deep copy
copy.setColor("Blue"); // doesn't affect original
copy.moveTo(20, 20);

System.out.println(original); // Circle{radius=10, color='Red', x=5, y=5}
System.out.println(copy); // Circle{radius=10, color='Blue', x=20, y=20}

Deep Clone with Mutable Referencesโ€‹

When objects contain mutable reference fields, you must deep-copy them:

public class Document implements Cloneable {
private String title;
private List<String> pages;
private Map<String, String> metadata;

public Document(String title, List<String> pages, Map<String, String> metadata) {
this.title = title;
this.pages = pages;
this.metadata = metadata;
}

// Deep copy constructor
public Document(Document source) {
this.title = source.title; // String is immutable โ€” safe
this.pages = new ArrayList<>(source.pages); // new list with same elements
this.metadata = new HashMap<>(source.metadata); // new map with same entries
}

@Override
public Document clone() {
return new Document(this);
}
}

// Usage
Document original = new Document("Design Patterns",
new ArrayList<>(List.of("Intro", "Creational")),
new HashMap<>(Map.of("author", "GoF")));

Document copy = original.clone();
copy.getPages().add("Structural"); // doesn't affect original
copy.getMetadata().put("year", "1994");

Prototype Registryโ€‹

A registry (or cache) of pre-configured prototypes that can be cloned on demand:

public class ShapeRegistry {
private final Map<String, Shape> prototypes = new HashMap<>();

public void register(String key, Shape prototype) {
prototypes.put(key, prototype);
}

public Shape get(String key) {
Shape prototype = prototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("No prototype registered for: " + key);
}
return prototype.clone();
}
}

// Setup
ShapeRegistry registry = new ShapeRegistry();

Circle defaultCircle = new Circle(50);
defaultCircle.setColor("White");
registry.register("default-circle", defaultCircle);

Rectangle header = new Rectangle(800, 60);
header.setColor("DarkBlue");
registry.register("header-bar", header);

// Usage โ€” clone from registry
Shape circle1 = registry.get("default-circle");
Shape circle2 = registry.get("default-circle");
// circle1 and circle2 are independent copies

Prototype vs Other Creational Patternsโ€‹

AspectPrototypeFactoryBuilder
Creates viaCloning an existing objectMethod call with type selectionStep-by-step construction
Best forExpensive initialization, many similar objectsDifferent types behind a common interfaceComplex objects with many parameters
Runtime flexibilityHigh โ€” clone any prototypeMedium โ€” factory must know typesLow โ€” builds one specific type

Real-World Examples in Javaโ€‹

Class/MethodDescription
Object.clone()Built-in shallow clone support
java.util.ArrayList(Collection)Copy constructor
java.util.Collections.unmodifiableList()Creates a wrapper, preserving original
Spring bean scopes (prototype)Creates a new instance for each request

๐Ÿ”„ Before & After: Why Prototype Mattersโ€‹

โŒ Without Prototype โ€” Expensive re-creation every timeโ€‹

// Each test creates a User from scratch โ€” slow and repetitive
@Test
void testVipDiscount() {
User user = new User();
user.setName("Alice");
user.setEmail("[email protected]");
user.setAddress(loadDefaultAddress()); // DB call!
user.setPreferences(loadDefaultPrefs()); // another DB call!
user.setStatus(UserStatus.VIP); // the only thing that varies
// ...
}

@Test
void testRegularDiscount() {
User user = new User();
user.setName("Alice"); // same setup repeated
user.setEmail("[email protected]");
user.setAddress(loadDefaultAddress()); // same DB call again
user.setPreferences(loadDefaultPrefs()); // and again
user.setStatus(UserStatus.REGULAR);
}

โœ… With Prototype โ€” Clone once, tweak what variesโ€‹

// Create the prototype once (expensive setup done ONCE)
private static final User PROTOTYPE = createBaseUser();

@Test
void testVipDiscount() {
User user = PROTOTYPE.clone();
user.setStatus(UserStatus.VIP); // only change what matters
}

@Test
void testRegularDiscount() {
User user = PROTOTYPE.clone();
user.setStatus(UserStatus.REGULAR);
}
// 10x faster test setup, zero repeated DB calls.

๐Ÿ’ผ Prototype in Spring & Enterprise Javaโ€‹

Spring's @Scope("prototype") โ€” Framework-Managed Prototypeโ€‹

Spring has a built-in prototype scope that creates a new instance for each injection/request:

@Component
@Scope("prototype")
public class ShoppingCart {
private final List<CartItem> items = new ArrayList<>();
public void addItem(CartItem item) { items.add(item); }
}

// Each user gets a FRESH cart โ€” Spring creates a new instance each time:
@Service
public class CheckoutService {
@Autowired
private Provider<ShoppingCart> cartProvider; // javax.inject.Provider

public ShoppingCart getNewCart() {
return cartProvider.get(); // new instance every call
}
}

Prototype Registry for Configuration Templatesโ€‹

@Component
public class ReportTemplateRegistry {
private final Map<String, ReportConfig> templates = new HashMap<>();

@PostConstruct
public void init() {
// Pre-configure expensive templates once
templates.put("monthly-sales", buildMonthlySalesTemplate());
templates.put("user-activity", buildUserActivityTemplate());
}

public ReportConfig getTemplate(String type) {
return templates.get(type).clone(); // clone, don't share!
}
}

Advantages & Disadvantagesโ€‹

AdvantagesDisadvantages
Avoids expensive object creationDeep cloning can be complex to implement correctly
Adds new types at runtime by cloning prototypesCircular references make deep cloning tricky
Reduces the need for factory subclassesMust maintain clone methods as class evolves
Independent of concrete classesShallow clone pitfalls if not handled carefully

Interview Questionsโ€‹

Q1: What is the Prototype pattern and how does it work?

The Prototype pattern creates new objects by copying existing ones rather than constructing from scratch. It provides a clone() method that produces a copy of the current object. This is efficient when object creation involves expensive operations like database queries, file reads, or complex initialization. Clients clone a configured prototype and modify the copy as needed.

Q2: What is the difference between shallow cloning and deep cloning?

Shallow cloning copies field values directly โ€” if fields are references to mutable objects, both original and clone share those references, so changes to one affect the other. Deep cloning recursively copies all referenced objects, creating a fully independent copy. Deep cloning is safer but more complex and expensive to implement correctly.

Q3: What are the common pitfalls of using the Prototype pattern?

The main pitfall is mismanaging shallow vs deep cloning. Shallow cloning can cause subtle bugs when cloned objects unknowingly share mutable state. Deep cloning can be difficult to implement correctly, especially with complex object graphs or circular references, and may cause performance issues if the object tree is large. The clone method must also be maintained as the class structure evolves.

Q4: How does Java's Object.clone() work and why is it considered problematic?

Object.clone() performs a field-by-field shallow copy and requires the class to implement Cloneable (a marker interface). It's considered problematic because: (1) it returns Object, requiring a cast; (2) it performs only a shallow copy; (3) Cloneable has no clone() method itself โ€” the method is on Object and protected; (4) it bypasses constructors, which can leave objects in an invalid state. Copy constructors are generally preferred.

Q5: When would you choose Prototype over Factory?

Choose Prototype when object initialization is expensive and you already have a configured instance to copy from. Choose Factory when you need to create objects of different types based on runtime conditions. Prototype is also useful when you want to defer the decision of what to clone until runtime, and when a prototype registry can serve as a flexible alternative to a complex factory hierarchy.


Advanced Editorial Pass: Prototype for Expensive Object Genesisโ€‹

When Prototype Is Effectiveโ€‹

  • Object creation cost is dominated by initialization, hydration, or graph assembly.
  • You need many similar instances with small controlled differences.
  • Runtime composition of baseline configurations is preferable to large factory trees.

Hidden Complexityโ€‹

  • Deep copy correctness is hard when graphs contain shared references.
  • Clone semantics become ambiguous with lazy fields and external resources.
  • Mutation after cloning can break assumptions about shared vs isolated state.

Senior-Level Guidanceโ€‹

  1. Make clone depth explicit in API and documentation.
  2. Prefer copy constructors/factory copy methods when clone() semantics are unclear.
  3. Add regression tests for aliasing bugs in nested object graphs.

๐Ÿ”„ Relations with Other Patternsโ€‹

  • Factory Method: Many designs start by using Factory Method (simpler) and naturally evolve toward Prototype or Abstract Factory as complexity increases. Prototype does not require deep inheritance hierarchies but necessitates a complex initialization (cloning) process.
  • Abstract Factory and Builder: Abstract Factory classes are often based on a set of Factory Methods, but you can also use Prototype to compose the methods on these classes.
  • Command: Prototypes can be very useful when you need to save exact copies of Commands into a history stack.
  • Composite and Decorator: Designs that make heavy use of Composite and Decorator often benefit from Prototype. You can clone complex structural graphs instead of reconstructing them from scratch.

โš–๏ธ Prototype vs. Similar Approachesโ€‹

AspectPrototype (clone)Copy ConstructorFactory MethodObject.clone()Serialization clone
MechanismCustom clone()new Foo(original)Method returns new instanceJVM shallow copySerialize + deserialize
Deep copyYou implement itYou implement itN/AโŒ Shallow onlyโœ… Automatic deep copy
Type safetyโœ… Covariant returnโœ… Compile-timeโœ… Interface-basedโŒ Returns Objectโœ… Generic
PerformanceFastFastDepends on creationFastest (native)Slow (I/O overhead)
When to pickPolymorphic cloning neededKnown concrete typesType selection neededLegacy code onlyComplex object graphs