Intent

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying that prototype. The prototype carries all the configuration; clones inherit it for free and then differ only where they need to.

The Problem

You're building a quotation system. Every quote starts from a long, carefully-configured base quote — the company's letterhead, payment terms, currency, tax rules, expiration policy, default line-item structure, footer disclaimers. Constructing one of these from scratch involves a dozen lookups: company settings from the database, default tax rates from a regional service, branding from a CMS, standard terms from a legal repository.

Now sales wants to send 200 personalized quotes from a campaign. The first version constructs each one from scratch:

PHP
foreach ($leads as $lead) {
    $quote = new Quote(
        company: $this->companyService->load(),
        terms:   $this->legalRepo->terms(),
        tax:     $this->taxService->rateFor($lead->region),
        // ... 9 more lookups ...
    );
    $quote->setLead($lead);
    $quote->setLineItems($lead->preferredProducts);
    $this->mailer->send($quote);
}

It works, but it runs 12 lookups × 200 quotes = 2,400 service calls. The quote object is essentially the same every time — only the lead-specific parts change. You're rebuilding the cathedral for every birthday card you put inside it.

The Solution

Prototype says: build the prototype once, do the expensive setup, then clone it for each variation. The clone inherits the configured state; you tweak only the fields that differ.

PHP
final class Quote
{
    // ... fields ...

    public function clone(): self
    {
        return clone $this;   // PHP, JS, Python, etc. all give you native cloning.
    }
}

$prototype = $this->buildBaseQuote();   // 12 lookups, run once.

foreach ($leads as $lead) {
    $quote = $prototype->clone();
    $quote->setLead($lead);
    $quote->setLineItems($lead->preferredProducts);
    $this->mailer->send($quote);
}

12 lookups instead of 2,400. The prototype is the recipe + the costly mise en place; clones are individual servings off the same prep.

Real-World Analogy

Cell division. A cell goes through hours of setup — replicating its DNA, building its organelles, doubling its membrane material. When it finally divides, two daughter cells appear, each carrying the full setup their parent did. They start as copies — and then they differentiate, becoming a skin cell or a nerve cell or a muscle cell.

Nature doesn't run the entire setup process every time it needs a new cell. It clones the configured one and lets it specialize from there. That's exactly what Prototype does in code.

Structure

Prototype pattern: a Prototype interface with a clone() method, implemented by ConcretePrototypes; clients hold a prototype and call clone() to produce variations.
Prototype: clone the configured one rather than running the whole construction.

Three roles you'll see in every Prototype implementation:

  • Prototype — the interface declaring clone() (or its language equivalent). Here: Quote with a clone() method.
  • Concrete Prototype — the configured instance you copy from. Holds all the state that's expensive to set up.
  • Client — code that holds a reference to a prototype and asks it to clone whenever a new variation is needed.

The defining property: cloning happens at runtime, with whatever state the prototype is currently in. That's a different shape from Factory Method (which picks a class) — Prototype lets you pick a configured instance.

Code Examples

Here's the quotation prototype in five languages. Notice that every modern language has a built-in cloning mechanism — but you decide whether the clone is deep (recursively copies nested objects) or shallow (shares them).

class Quote {
  constructor(
    public company: CompanyInfo,
    public terms: string,
    public tax: number,
    public lead: Lead | null = null,
    public lineItems: LineItem[] = [],
  ) {}

  clone(): Quote {
    // Shallow-clone the object, then deep-clone the array we'll mutate per copy.
    return new Quote(
      this.company,
      this.terms,
      this.tax,
      this.lead,
      this.lineItems.map(i => ({ ...i })),
    );
  }
}

const prototype = await buildBaseQuote();   // 12 service calls, run once.

for (const lead of leads) {
  const quote = prototype.clone();
  quote.lead = lead;
  quote.lineItems = lead.preferredProducts.map(toLineItem);
  await mailer.send(quote);
}
import copy
from dataclasses import dataclass, field

@dataclass
class Quote:
    company: object
    terms: str
    tax: float
    lead: object | None = None
    line_items: list = field(default_factory=list)

    def clone(self):
        # copy.deepcopy gives a true independent copy; copy.copy is shallow.
        return copy.deepcopy(self)

prototype = build_base_quote()   # 12 service calls, run once.

for lead in leads:
    quote = prototype.clone()
    quote.lead = lead
    quote.line_items = [to_line_item(p) for p in lead.preferred_products]
    mailer.send(quote)
public final class Quote implements Cloneable {
    public CompanyInfo company;
    public String      terms;
    public double      tax;
    public Lead        lead;
    public List<LineItem> lineItems = new ArrayList<>();

    @Override
    public Quote clone() {
        try {
            Quote copy = (Quote) super.clone();
            copy.lineItems = new ArrayList<>(this.lineItems);   // shallow-copy the list
            return copy;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }
}

// Use:
Quote prototype = buildBaseQuote();   // 12 service calls, run once.
for (Lead lead : leads) {
    Quote quote = prototype.clone();
    quote.lead = lead;
    quote.lineItems = lead.preferredProducts().stream().map(this::toLineItem).toList();
    mailer.send(quote);
}
<?php

namespace App\Quotes;

final class Quote
{
    /** @var LineItem[] */
    public array $lineItems = [];

    public function __construct(
        public CompanyInfo $company,
        public string      $terms,
        public float       $tax,
        public ?Lead       $lead = null,
    ) {}

    // PHP's `clone` keyword does a shallow copy. __clone() lets us deep-copy fields that need it.
    public function __clone(): void
    {
        $this->lineItems = array_map(fn ($i) => clone $i, $this->lineItems);
    }
}

$prototype = $this->buildBaseQuote();   // 12 service calls, run once.

foreach ($leads as $lead) {
    $quote = clone $prototype;
    $quote->lead      = $lead;
    $quote->lineItems = array_map(fn ($p) => $this->toLineItem($p), $lead->preferredProducts);
    $this->mailer->send($quote);
}
package quotes

// Go has no inheritance and no built-in clone. The idiomatic prototype is a
// constructor function that copies all fields explicitly.

type Quote struct {
    Company   CompanyInfo
    Terms     string
    Tax       float64
    Lead      *Lead
    LineItems []LineItem
}

// Clone returns a deep copy of the quote. The slice is freshly allocated so
// callers can mutate it without affecting the prototype.
func (q *Quote) Clone() *Quote {
    items := make([]LineItem, len(q.LineItems))
    copy(items, q.LineItems)
    return &Quote{
        Company:   q.Company,
        Terms:     q.Terms,
        Tax:       q.Tax,
        Lead:      q.Lead,
        LineItems: items,
    }
}

// Use:
// prototype := buildBaseQuote()
// for _, lead := range leads {
//     quote := prototype.Clone()
//     quote.Lead = lead
//     quote.LineItems = mapToLineItems(lead.PreferredProducts)
//     mailer.Send(quote)
// }

The expensive setup runs once. Each variation gets a configured starting point and tweaks only what's specific to it. The pattern's win is amortizing construction cost across many variants.

When to Use It

Reach for Prototype when you can answer "yes" to any of these:

  • Object construction is genuinely expensive — many service calls, large memory allocations, complex defaulting — and you need many variants.
  • The variants share most of their state. If clones differ in 80% of their fields, you're not really cloning — you're constructing.
  • You want to capture configuration at runtime and reuse it. A user customizes a settings panel; you clone the customized version for the next user with the same role.
  • The set of object types is decided dynamically. Factory Method needs a class hierarchy fixed at compile time; Prototype lets the prototype itself determine the type.
  • You're testing. Building a fully-populated test object once and cloning it per test case beats writing a fixture function with 12 parameters.

If your objects are cheap to construct and don't share much state, just construct them. Prototype earns its keep at scale, with shared setup cost.

Pros and Cons

Pros

  • Avoids re-running expensive construction for every new instance.
  • Lets you capture and reuse runtime configuration — not just compile-time defaults.
  • Composes well with Factory Method and Memento when you want object pools or undo systems.
  • Most languages have built-in cloning mechanics — the pattern often takes one method.

Cons

  • Deep vs shallow clone is a real decision. A shallow clone shares nested objects; mutating one affects the other. A deep clone is expensive and easy to get wrong (circular references, unsupported types, secrets accidentally copied).
  • Cloning sensitive state is dangerous. Database handles, file handles, network sockets, decrypted secrets — if any of these are in the object, cloning can produce a security or correctness bug.
  • Tracking which state needs deep-copying is tedious and easy to forget when the class evolves.
  • Some objects shouldn't be clonable — singletons, identity-bearing objects (a User with primary key), things that own external resources.

Pro Tips

  • Default to immutability. When the prototype's fields are immutable, clones share them safely. Structural sharing is the cleanest deep clone — because nothing actually got copied.
  • Make clone() return a fully usable object. Half-cloned states ("this clone shares X but you must replace Y before using") are bug magnets.
  • Document deep vs shallow. Whatever you decide, spell it out in the method's docs or its name (shallowCopy() vs deepCopy()).
  • JSON serialize/deserialize is an honest deep clone in many languages. Slow for big objects, but it handles nested structures correctly and forces you to keep the object serializable.
  • Don't clone objects with identity. A User with id: 42 shouldn't have a clone with id: 42 — that's two of the same person. Reset identity-bearing fields explicitly in clone().

Relations with Other Patterns

  • Factory Method is the alternative when variation comes from class choice; Prototype is the alternative when variation comes from runtime configuration. Many systems use both: a Factory Method that picks which Prototype to clone.
  • Abstract Factory can be implemented as a registry of prototypes — each "create" call clones the right prototype rather than calling new.
  • Composite trees often need cloning together — clone the root, recursively clone children.
  • Memento has a similar "snapshot then restore" mechanic, but Memento is for the same object's previous state; Prototype is for new objects derived from a configured one.
  • Object pools are usually backed by Prototype: configure one expensive object, clone for every borrower, return the clone to be reset and reused.

Final Tips

The Prototype I've shipped most often isn't for production hot paths — it's for tests. A userPrototype() function returns a fully-populated User with sensible defaults; each test clones it and tweaks the one field it cares about. The fixture stays small, the tests stay readable, and adding a new required field to User means updating one prototype, not 50 test fixtures.

Reach for Prototype when configuration is expensive and variation is cheap. The pattern feels obvious once you see it — and a lot of "I'll just construct another one from scratch" code in the wild is one good prototype away from being faster, smaller, and easier to maintain.