Skip to content

Prototype Pattern

Clone existing objects instead of creating new ones - save time and resources!

Prototype Pattern: Cloning Objects Efficiently

Section titled “Prototype Pattern: Cloning Objects Efficiently”

Now let’s dive into the Prototype Pattern - a creational design pattern that lets you create new objects by copying existing ones (prototypes) instead of creating them from scratch.

Imagine you’re ordering custom pizzas. Instead of describing each pizza from scratch every time, you can say “I want the same as last time, but with extra cheese!” The Prototype Pattern works the same way!

The Prototype Pattern lets you create new objects by cloning existing instances. Instead of going through expensive object creation, you copy a prototype and customize it.

The Prototype Pattern is useful when:

  1. Object creation is expensive - Database queries, network calls, complex calculations
  2. You need many similar objects - Objects with similar structure but different values
  3. You want to avoid subclassing - Don’t want to create a class hierarchy
  4. Runtime configuration - Objects need to be configured at runtime
  5. You want to hide creation complexity - Client doesn’t need to know how objects are created

What Happens If We Don’t Use Prototype Pattern?

Section titled “What Happens If We Don’t Use Prototype Pattern?”

Without the Prototype Pattern, you might:

  • Recreate expensive objects - Waste time and resources on expensive initialization
  • Duplicate initialization code - Same setup code repeated everywhere
  • Create unnecessary subclasses - Class explosion for slight variations
  • Tight coupling - Client code depends on concrete classes
  • Performance issues - Slow object creation impacts application performance

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Prototype Pattern works in practice - showing the cloning process:

sequenceDiagram
    participant Client
    participant Prototype as PizzaPrototype
    participant Clone1 as CustomPizza1
    participant Clone2 as CustomPizza2
    
    Note over Client,Clone2: Expensive initialization happens once
    
    Client->>Prototype: create_prototype()
    activate Prototype
    Note over Prototype: Expensive setup:<br/>- Database queries<br/>- Network calls<br/>- Complex calculations
    Prototype-->>Client: prototype instance
    deactivate Prototype
    
    Note over Client,Clone2: Fast cloning for similar objects
    
    Client->>Prototype: clone()
    activate Prototype
    Prototype->>Clone1: create copy
    activate Clone1
    Clone1-->>Prototype: cloned instance
    deactivate Clone1
    Prototype-->>Client: Clone1
    deactivate Prototype
    
    Client->>Clone1: customize(toppings)
    activate Clone1
    Clone1-->>Client: customized
    deactivate Clone1
    
    Client->>Prototype: clone()
    activate Prototype
    Prototype->>Clone2: create copy
    activate Clone2
    Clone2-->>Prototype: cloned instance
    deactivate Clone2
    Prototype-->>Client: Clone2
    deactivate Prototype
    
    Note over Client,Clone2: Much faster than<br/>creating from scratch!

You’re building a pizza ordering system where customers can order custom pizzas. Creating each pizza from scratch involves expensive operations (loading templates, fetching prices, validating ingredients). Without Prototype Pattern:

bad_pizza.py
# ❌ Without Prototype Pattern - Expensive creation every time!
class Pizza:
def __init__(self, size: str, crust: str, toppings: list):
self.size = size
self.crust = crust
self.toppings = toppings
# Expensive operations happen every time!
self._load_template() # Database query
self._fetch_prices() # Network call
self._validate_ingredients() # Complex validation
print(f"⏱️ Creating pizza from scratch (expensive!)")
def _load_template(self):
# Simulate expensive database query
import time
time.sleep(0.1) # Expensive!
print("📊 Loading pizza template from database...")
def _fetch_prices(self):
# Simulate expensive network call
import time
time.sleep(0.1) # Expensive!
print("🌐 Fetching prices from API...")
def _validate_ingredients(self):
# Simulate complex validation
import time
time.sleep(0.05) # Expensive!
print("✅ Validating ingredients...")
def describe(self):
return f"{self.size} {self.crust} pizza with {', '.join(self.toppings)}"
# Problem: Every pizza creation is expensive!
pizza1 = Pizza("large", "thin", ["cheese", "tomato"]) # Expensive!
pizza2 = Pizza("large", "thin", ["cheese", "pepperoni"]) # Expensive again!
pizza3 = Pizza("large", "thin", ["cheese", "mushrooms"]) # Expensive again!
# All three pizzas are similar but we pay the cost 3 times!

Problems:

  • Expensive operations repeated for every pizza
  • Slow performance when creating many similar pizzas
  • Wasted resources on redundant operations
  • No way to reuse expensive initialization
classDiagram
    class PizzaPrototype {
        <<abstract>>
        +clone() PizzaPrototype
        +customize(toppings) void
    }
    class Pizza {
        -size: str
        -crust: str
        -toppings: List
        +clone() Pizza
        +customize(toppings) void
        +describe() str
    }
    class MargheritaPrototype {
        +clone() Pizza
    }
    class PepperoniPrototype {
        +clone() Pizza
    }
    class Client {
        +create_pizzas() void
    }
    
    PizzaPrototype <|-- Pizza : implements
    Pizza <|-- MargheritaPrototype : extends
    Pizza <|-- PepperoniPrototype : extends
    Client --> PizzaPrototype : clones
    
    note for PizzaPrototype "Expensive initialization<br/>happens once"
    note for Pizza "Clone is fast -<br/>just copy data"
pizza_prototype.py
import copy
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define the Prototype interface
class PizzaPrototype(ABC):
"""Prototype interface for clonable pizzas"""
@abstractmethod
def clone(self) -> 'PizzaPrototype':
"""Create a copy of this pizza"""
pass
@abstractmethod
def customize(self, toppings: List[str]) -> None:
"""Customize the pizza"""
pass
# Step 2: Create concrete Prototype class
class Pizza(PizzaPrototype):
"""Pizza class that can be cloned"""
def __init__(self, size: str, crust: str, toppings: List[str]):
self.size = size
self.crust = crust
self.toppings = toppings
# Expensive operations happen only once during prototype creation
self._load_template()
self._fetch_prices()
self._validate_ingredients()
print(f"⏱️ Creating pizza prototype (expensive - happens once!)")
def _load_template(self):
"""Expensive database query - happens once"""
import time
time.sleep(0.1)
print("📊 Loading pizza template from database...")
def _fetch_prices(self):
"""Expensive network call - happens once"""
import time
time.sleep(0.1)
print("🌐 Fetching prices from API...")
def _validate_ingredients(self):
"""Complex validation - happens once"""
import time
time.sleep(0.05)
print("✅ Validating ingredients...")
def clone(self) -> 'Pizza':
"""Clone the pizza - fast operation!"""
print(f"⚡ Cloning pizza (fast!)")
# Use deep copy to create independent copy
cloned = copy.deepcopy(self)
return cloned
def customize(self, toppings: List[str]) -> None:
"""Customize the cloned pizza"""
self.toppings = toppings
print(f"✨ Customized with toppings: {', '.join(toppings)}")
def describe(self) -> str:
return f"{self.size} {self.crust} pizza with {', '.join(self.toppings)}"
# Step 3: Create prototype instances
class PizzaPrototypeRegistry:
"""Registry to store and retrieve prototypes"""
def __init__(self):
self._prototypes = {}
def register(self, name: str, prototype: PizzaPrototype) -> None:
"""Register a prototype"""
self._prototypes[name] = prototype
print(f"📝 Registered prototype: {name}")
def get(self, name: str) -> PizzaPrototype:
"""Get a clone of the prototype"""
if name not in self._prototypes:
raise ValueError(f"Prototype {name} not found")
prototype = self._prototypes[name]
return prototype.clone()
# Step 4: Use the pattern
def main():
# Create prototypes once (expensive)
margherita_prototype = Pizza("large", "thin", ["cheese", "tomato"])
pepperoni_prototype = Pizza("large", "thin", ["cheese", "pepperoni"])
# Register prototypes
registry = PizzaPrototypeRegistry()
registry.register("margherita", margherita_prototype)
registry.register("pepperoni", pepperoni_prototype)
print("\n" + "="*50)
print("Creating pizzas by cloning (fast!)")
print("="*50 + "\n")
# Clone pizzas (fast!)
pizza1 = registry.get("margherita")
pizza1.customize(["cheese", "tomato", "basil"])
pizza2 = registry.get("margherita")
pizza2.customize(["cheese", "tomato", "olives"])
pizza3 = registry.get("pepperoni")
pizza3.customize(["cheese", "pepperoni", "onions"])
print(f"\n🍕 Pizza 1: {pizza1.describe()}")
print(f"🍕 Pizza 2: {pizza2.describe()}")
print(f"🍕 Pizza 3: {pizza3.describe()}")
if __name__ == "__main__":
main()

Real-World Software Example: Document Template System

Section titled “Real-World Software Example: Document Template System”

Now let’s see a realistic software example - a document generation system where creating documents involves expensive database queries and template loading.

You’re building a document generation system. Creating each document involves loading templates from database, fetching user data, and formatting. Without Prototype Pattern:

bad_documents.py
# ❌ Without Prototype Pattern - Expensive creation every time!
class Document:
def __init__(self, template_name: str, user_data: dict):
self.template_name = template_name
self.user_data = user_data
# Expensive operations every time!
self._load_template() # Database query
self._fetch_user_data() # Network call
self._format_document() # Complex formatting
print(f"⏱️ Creating document from scratch (expensive!)")
def _load_template(self):
import time
time.sleep(0.2) # Expensive database query
print("📊 Loading template from database...")
def _fetch_user_data(self):
import time
time.sleep(0.15) # Expensive network call
print("🌐 Fetching user data from API...")
def _format_document(self):
import time
time.sleep(0.1) # Complex formatting
print("📝 Formatting document...")
def render(self):
return f"Document: {self.template_name} for {self.user_data.get('name')}"
# Problem: Every document creation is expensive!
doc1 = Document("invoice", {"name": "John", "amount": 100}) # Expensive!
doc2 = Document("invoice", {"name": "Jane", "amount": 200}) # Expensive again!
doc3 = Document("invoice", {"name": "Bob", "amount": 300}) # Expensive again!
# All invoices use the same template but we pay the cost 3 times!

Problems:

  • Expensive template loading repeated for every document
  • Slow performance when generating many documents
  • Wasted resources on redundant operations
  • No way to reuse template loading
document_prototype.py
import copy
from abc import ABC, abstractmethod
from typing import Dict, Any
# Step 1: Define the Prototype interface
class DocumentPrototype(ABC):
"""Prototype interface for clonable documents"""
@abstractmethod
def clone(self) -> 'DocumentPrototype':
"""Create a copy of this document"""
pass
@abstractmethod
def customize(self, user_data: Dict[str, Any]) -> None:
"""Customize the document with user data"""
pass
# Step 2: Create concrete Prototype class
class Document(DocumentPrototype):
"""Document class that can be cloned"""
def __init__(self, template_name: str, user_data: Dict[str, Any] = None):
self.template_name = template_name
self.user_data = user_data or {}
# Expensive operations happen only once during prototype creation
self._load_template()
self._fetch_template_data()
self._format_template()
print(f"⏱️ Creating document prototype (expensive - happens once!)")
def _load_template(self):
"""Expensive database query - happens once"""
import time
time.sleep(0.2)
print("📊 Loading template from database...")
def _fetch_template_data(self):
"""Expensive network call - happens once"""
import time
time.sleep(0.15)
print("🌐 Fetching template data from API...")
def _format_template(self):
"""Complex formatting - happens once"""
import time
time.sleep(0.1)
print("📝 Formatting template...")
def clone(self) -> 'Document':
"""Clone the document - fast operation!"""
print(f"⚡ Cloning document (fast!)")
cloned = copy.deepcopy(self)
return cloned
def customize(self, user_data: Dict[str, Any]) -> None:
"""Customize the cloned document"""
self.user_data = user_data
print(f"✨ Customized with user data: {user_data.get('name')}")
def render(self) -> str:
return f"Document: {self.template_name} for {self.user_data.get('name')}"
# Step 3: Create prototype registry
class DocumentPrototypeRegistry:
"""Registry to store and retrieve document prototypes"""
def __init__(self):
self._prototypes = {}
def register(self, name: str, prototype: DocumentPrototype) -> None:
"""Register a prototype"""
self._prototypes[name] = prototype
print(f"📝 Registered document prototype: {name}")
def get(self, name: str) -> DocumentPrototype:
"""Get a clone of the prototype"""
if name not in self._prototypes:
raise ValueError(f"Prototype {name} not found")
prototype = self._prototypes[name]
return prototype.clone()
# Step 4: Use the pattern
def main():
# Create prototypes once (expensive)
invoice_prototype = Document("invoice")
report_prototype = Document("report")
# Register prototypes
registry = DocumentPrototypeRegistry()
registry.register("invoice", invoice_prototype)
registry.register("report", report_prototype)
print("\n" + "="*50)
print("Generating documents by cloning (fast!)")
print("="*50 + "\n")
# Clone documents (fast!)
doc1 = registry.get("invoice")
doc1.customize({"name": "John", "amount": 100})
doc2 = registry.get("invoice")
doc2.customize({"name": "Jane", "amount": 200})
doc3 = registry.get("report")
doc3.customize({"name": "Bob", "department": "Sales"})
print(f"\n📄 Document 1: {doc1.render()}")
print(f"📄 Document 2: {doc2.render()}")
print(f"📄 Document 3: {doc3.render()}")
if __name__ == "__main__":
main()

There are several ways to implement the Prototype Pattern:

Shallow Copy: Copies object references (faster but shared references)

shallow_copy.py
import copy
class Pizza:
def __init__(self, toppings):
self.toppings = toppings # List is mutable
def shallow_clone(self):
return copy.copy(self) # Shallow copy
# Problem: Shared reference!
pizza1 = Pizza(["cheese", "tomato"])
pizza2 = pizza1.shallow_clone()
pizza2.toppings.append("pepperoni") # Modifies pizza1.toppings too!

Deep Copy: Copies all nested objects (slower but independent)

deep_copy.py
import copy
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
def deep_clone(self):
return copy.deepcopy(self) # Deep copy
# Solution: Independent copies!
pizza1 = Pizza(["cheese", "tomato"])
pizza2 = pizza1.deep_clone()
pizza2.toppings.append("pepperoni") # Only modifies pizza2.toppings

A registry pattern to manage prototypes:

prototype_registry.py
class PrototypeRegistry:
def __init__(self):
self._prototypes = {}
def register(self, name: str, prototype):
self._prototypes[name] = prototype
def get(self, name: str):
return self._prototypes[name].clone()
def unregister(self, name: str):
del self._prototypes[name]

Use Prototype Pattern when:

Object creation is expensive - Database queries, network calls, complex calculations
You need many similar objects - Objects with similar structure but different values
You want to avoid subclassing - Don’t want to create a class hierarchy
Runtime configuration - Objects need to be configured at runtime
You want to hide creation complexity - Client doesn’t need to know how objects are created

Don’t use Prototype Pattern when:

Object creation is cheap - If creation is fast, cloning adds unnecessary complexity
Objects are very different - If objects differ significantly, cloning doesn’t help
You need deep inheritance - Subclassing might be better
Cloning is complex - If cloning is as expensive as creation, pattern doesn’t help


Mistake 1: Shallow Copy When Deep Copy is Needed

Section titled “Mistake 1: Shallow Copy When Deep Copy is Needed”
shallow_copy_mistake.py
# ❌ Bad: Shallow copy with mutable objects
import copy
class Pizza:
def __init__(self, toppings):
self.toppings = toppings # Mutable list!
def clone(self):
return copy.copy(self) # Shallow copy - problem!
pizza1 = Pizza(["cheese"])
pizza2 = pizza1.clone()
pizza2.toppings.append("pepperoni") # Also modifies pizza1!
# ✅ Good: Deep copy for mutable objects
class Pizza:
def clone(self):
return copy.deepcopy(self) # Deep copy - correct!

Mistake 2: Not Implementing Clone Properly

Section titled “Mistake 2: Not Implementing Clone Properly”
bad_clone.py
# ❌ Bad: Clone doesn't create independent copy
class Pizza:
def clone(self):
return self # Returns same instance - wrong!
# ✅ Good: Clone creates new instance
class Pizza:
def clone(self):
return copy.deepcopy(self) # Creates new instance - correct!
unnecessary_clone.py
# ❌ Bad: Cloning when creation is cheap
class SimpleObject:
def __init__(self, value):
self.value = value # Simple creation
def clone(self):
return copy.deepcopy(self) # Unnecessary complexity!
# ✅ Better: Direct creation for simple objects
obj1 = SimpleObject(10)
obj2 = SimpleObject(10) # Just create new one!

  1. Performance - Cloning is faster than expensive object creation
  2. Resource Efficiency - Reuse expensive initialization
  3. Flexibility - Clone and customize objects at runtime
  4. Reduced Subclassing - Don’t need class hierarchy for variations
  5. Hide Complexity - Client doesn’t need to know creation details

Prototype Pattern is a creational design pattern that lets you create new objects by copying existing instances (prototypes) instead of creating them from scratch.

  • Expensive object creation - Clone is faster than expensive initialization
  • Many similar objects - Objects with similar structure but different values
  • Avoid subclassing - Don’t need class hierarchy for variations
  • Runtime configuration - Objects configured at runtime
  • Resource efficiency - Reuse expensive initialization
  1. Create prototype - Create an instance with expensive initialization
  2. Register prototype - Store prototype in registry (optional)
  3. Clone prototype - Create copy of prototype (fast!)
  4. Customize clone - Modify cloned object as needed
Client → Prototype.clone() → Clone Instance
  • Prototype Interface - Defines clone method
  • Concrete Prototype - Implements cloning
  • Client - Uses prototype to create objects
  • Registry - Manages prototypes (optional)
class Pizza:
def __init__(self):
# Expensive initialization
self._load_template()
def clone(self):
return copy.deepcopy(self) # Fast clone!
# Usage
prototype = Pizza() # Expensive - happens once
pizza1 = prototype.clone() # Fast!
pizza2 = prototype.clone() # Fast!

✅ Expensive object creation
✅ Many similar objects needed
✅ Want to avoid subclassing
✅ Runtime configuration required
✅ Need to hide creation complexity

❌ Object creation is cheap
❌ Objects are very different
❌ Need deep inheritance
❌ Cloning is as expensive as creation

  • Prototype Pattern = Clone existing objects instead of creating new
  • Prototype = Template object with expensive initialization
  • Clone = Fast copy operation
  • Benefit = Performance and resource efficiency
  • Use Case = Expensive object creation with similar objects
class Prototype:
def clone(self):
return copy.deepcopy(self)
# Usage
prototype = Prototype() # Expensive initialization
clone1 = prototype.clone() # Fast!
clone2 = prototype.clone() # Fast!
  • Prototype Pattern clones existing objects
  • It’s useful when creation is expensive
  • Use deep copy for objects with mutable nested structures
  • Clone is faster than expensive initialization
  • Don’t use it when creation is cheap - avoid over-engineering!

What to say:

“Prototype Pattern is a creational design pattern that lets you create new objects by copying existing instances instead of creating them from scratch. It’s useful when object creation is expensive, and you need many similar objects.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of when to use it
  • Indicates you can explain concepts clearly

Must mention:

  • Expensive object creation - Database queries, network calls
  • Many similar objects - Objects with similar structure
  • Avoid subclassing - Don’t want class hierarchy
  • Runtime configuration - Objects configured at runtime

Example scenario to give:

“I’d use Prototype Pattern when building a document generation system. Loading templates from database is expensive. Instead of loading the template every time, I create a prototype once, then clone it for each document with different user data.”

Must discuss:

  • Shallow Copy: Copies object references (faster but shared references)
  • Deep Copy: Copies all nested objects (slower but independent)
  • When to use each: Deep copy for mutable nested structures

Example to give:

“In Python, I use copy.deepcopy() for objects with mutable nested structures like lists or dictionaries. Shallow copy would share references, causing bugs when modifying cloned objects.”

Benefits to mention:

  • Performance - Cloning is faster than expensive creation
  • Resource Efficiency - Reuse expensive initialization
  • Flexibility - Clone and customize at runtime
  • Reduced Subclassing - Don’t need class hierarchy

Trade-offs to acknowledge:

  • Complexity - Need to implement cloning correctly
  • Memory - Prototypes consume memory
  • Not always faster - If creation is cheap, cloning adds overhead

Q: “What’s the difference between Prototype Pattern and Factory Pattern?”

A:

“Factory Pattern creates objects based on input, while Prototype Pattern clones existing objects. Factory is used when you need different types of objects, Prototype is used when you need many similar objects and creation is expensive.”

Q: “When would you use shallow copy vs deep copy?”

A:

“I use shallow copy when objects don’t have mutable nested structures - it’s faster. I use deep copy when objects have mutable nested structures like lists or dictionaries - it ensures independent copies and prevents bugs from shared references.”

Q: “How do you implement Prototype Pattern in a multi-threaded environment?”

A:

“I ensure the clone method is thread-safe. The prototype itself should be immutable or protected with synchronization. When cloning, I create a new instance with copied data, which is naturally thread-safe since each clone is independent.”

Before your interview, make sure you can:

  • Define Prototype Pattern clearly in one sentence
  • Explain when to use it (with examples)
  • Describe shallow vs deep copy
  • Implement Prototype Pattern from scratch
  • Compare with other creational patterns
  • List benefits and trade-offs
  • Identify common mistakes
  • Give 2-3 real-world examples
  • Discuss thread safety considerations

Remember: Prototype Pattern is about cloning expensive objects efficiently - clone once, customize many times! 🎯