# Prototype

> Create new objects by copying a configured existing instance, so callers get a ready-made variant without knowing its class or setup.

- kind: pattern
- category: creational
- aliases: Clone
- canonical: https://aipatterns.dev/design-patterns/prototype
- inspired-by: https://refactoring.guru/design-patterns/prototype

## Intent

Make an object responsible for copying itself. Clients keep a set of preconfigured instances, the prototypes, and create new objects by cloning one and adjusting the copy, instead of calling a constructor and repeating the setup.

## Forces

- **Expensive or detailed setup** vs **many similar objects**: rebuilding the same configuration by hand for every instance is slow and error-prone.
- **Copying from outside** vs **encapsulation**: a client can't copy private fields it can't see, and shouldn't need to know them.
- **Shallow copy** vs **independent copies**: copying references to nested lists and maps makes the clone and the original share state.
- **Configuration known at runtime** vs **classes known at compile time**: templates loaded from config files don't map to one subclass each.

## Therefore

**Give the object a `clone()` method that returns an independent copy of itself, and create variants by cloning a registered template and changing the copy.**

The object knows its own fields, so it is the right place to copy them, including private ones. A small registry maps names to prototypes. Before writing `clone()` by hand, check what the language already gives you: `copy.deepcopy` in Python, copy constructors or records in Java, struct assignment plus `maps.Clone` and `slices.Clone` in Go, spread syntax or `structuredClone` in JavaScript. The pattern's value is the *template registry* idea and the guarantee of a correct deep copy, not the method name.

## Structure

```mermaid
classDiagram
  class Prototype {
    <<interface>>
    +clone() Prototype
  }
  class AlertRule {
    -string name
    -int threshold
    -Map labels
    -string[] notify
    +clone() AlertRule
  }
  class RuleCatalog {
    -Map templates
    +register(key, rule) void
    +create(key) AlertRule
  }
  class Client {
    +provision(service) AlertRule
  }
  Prototype <|.. AlertRule
  RuleCatalog o-- Prototype : templates
  Client ..> RuleCatalog : asks for copies
```

### Participants

- **Prototype** (`Prototype`): declares `clone()`. Often unnecessary as a separate type when the language has a copy convention.
- **Concrete prototype** (`AlertRule`): copies itself, including deep copies of mutable collections.
- **Registry** (`RuleCatalog`): optional; stores named prototypes and hands out clones, never the originals.
- **Client**: asks for a clone by name and customizes the copy.

## Use it when

- Many objects differ from a few templates only in a handful of fields, and the templates come from configuration or user input.
- Creating an object from scratch is expensive (parsing, network calls, computed defaults) but copying a ready one is cheap.
- Client code must copy objects whose concrete class it doesn't know, for example items behind an interface in an editor or a workflow designer.
- You want "duplicate" or "save as new" behavior and the object owns private state the caller can't reach.

## Do not use it when

- The state is plain data and a copy has no rules of its own. The built-in copiers cover it: Python's `copy.copy` / `copy.deepcopy` and `dataclasses.replace`, Java records with wither methods, Go struct assignment, and JavaScript spread or `structuredClone`. Prototype earns its `clone()` only when copying needs rules they don't know - redacting fields, regenerating identity, deep-copying selected references.
- The object is immutable. Share the instance; there is nothing to copy. To get a variant, create a new value with the changed field (`replace(rule, threshold=1500)`).
- The object holds resources that can't be duplicated meaningfully: open connections, file handles, locks, or identity such as database primary keys. Cloning these causes subtle bugs. Use a factory that builds fresh ones.
- There are only a few fixed variants known at compile time. A factory function per variant is clearer than a registry of instances.
- In Java, don't reach for `Cloneable` and `Object.clone()`. They skip constructors, copy shallowly by default and need casts. Write a copy constructor or a static `copyOf`.

## Application grill

**Inspect first:** the expensive setups that would be copied (configured clients, parsed config, game objects); the language's cloning support (`structuredClone`, copy constructors, clone traits); which fields hold non-copyable resources (sockets, connections, timers) today.

**Missing pieces:** none beyond the language: `structuredClone` or a copy constructor. A deep-copy library only when the graph is cyclic or copying must skip typed fields.

**Ask before you build:**

- **Scale:** what does a copy cost versus a rebuild, measured?
- **Performance:** do copies run per request or per element where allocation shows?
- **Reliability:** which fields must be excluded or reset in copies (connections, caches, clock handles), and who owns that list?
- **Budget:** is a prototype pool actually cheaper than constructing fresh?
- **Implementation:** is the copy shallow or deep per field, and where is that documented?

## Implementation steps

1. Decide what "independent copy" means for each field: immutable values can be shared, mutable collections and nested objects must be copied, and identity fields (IDs, handles) must be reset or excluded.
2. Implement `clone()` on the class, or a copy constructor, using the language's copy facilities where they are correct.
3. Write a test that mutates every collection on the clone and asserts the original is unchanged.
4. If templates are named, add a registry that stores prototypes and always returns clones from `create()`.
5. Load or register the templates once at startup, and replace hand-repeated setup code with `create(key)` plus the field changes.

## Example

A monitoring platform ships alert rule templates ("high latency", "error spike"). Teams provision rules for each service by copying a template and adjusting labels, thresholds and notification targets. Templates must never change when one team edits its copy.

```ts title="alert-rules.ts"
interface Prototype<T> {
  clone(): T;
}

class AlertRule implements Prototype<AlertRule> {
  constructor(
    public name: string,
    public metric: string,
    public threshold: number,
    public forMinutes: number,
    public labels: Map<string, string>,
    public notify: string[],
  ) {}

  // structuredClone would drop the class (and its methods); copy explicitly.
  clone(): AlertRule {
    const { name, metric, threshold, forMinutes } = this;
    return new AlertRule(name, metric, threshold, forMinutes, new Map(this.labels), [...this.notify]);
  }
}

class RuleCatalog {
  private readonly templates = new Map<string, AlertRule>();

  register(key: string, rule: AlertRule): void {
    this.templates.set(key, rule);
  }

  create(key: string): AlertRule {
    const template = this.templates.get(key);
    if (!template) throw new Error(`unknown template: ${key}`);
    return template.clone();
  }
}

// Templates usually come from config at startup.
const catalog = new RuleCatalog();
const labels = new Map([["severity", "page"], ["team", "platform"]]);
catalog.register("high-latency", new AlertRule("high-latency", "http_p99_ms", 800, 5, labels, ["oncall:platform"]));

const checkout = catalog.create("high-latency");
checkout.name = "checkout-latency";
checkout.labels.set("service", "checkout");
checkout.notify.push("chat:#checkout");

const search = catalog.create("high-latency");
search.name = "search-latency";
search.threshold = 1500;

console.log(checkout.labels.size, checkout.notify.length); // 3 2
console.log(search.labels.size, search.notify.length, search.threshold); // 2 1 1500
```

```python title="alert_rules.py"
import copy
from dataclasses import dataclass, field

@dataclass
class AlertRule:
    name: str
    metric: str
    threshold: float
    for_minutes: int
    labels: dict[str, str] = field(default_factory=dict)
    notify: list[str] = field(default_factory=list)

    def clone(self) -> "AlertRule":
        # The standard library already knows how to deep-copy plain data.
        return copy.deepcopy(self)

class RuleCatalog:
    def __init__(self) -> None:
        self._templates: dict[str, AlertRule] = {}

    def register(self, key: str, rule: AlertRule) -> None:
        self._templates[key] = rule

    def create(self, key: str) -> AlertRule:
        if key not in self._templates:
            raise KeyError(f"unknown template: {key}")
        return self._templates[key].clone()

if __name__ == "__main__":
    catalog = RuleCatalog()
    catalog.register(
        "high-latency",
        AlertRule("high-latency", "http_p99_ms", 800, 5,
                  {"severity": "page", "team": "platform"}, ["oncall:platform"]),
    )

    checkout = catalog.create("high-latency")
    checkout.name = "checkout-latency"
    checkout.labels["service"] = "checkout"
    checkout.notify.append("chat:#checkout")

    search = catalog.create("high-latency")
    search.name = "search-latency"
    search.threshold = 1500

    print(len(checkout.labels), len(checkout.notify))  # 3 2
    print(len(search.labels), len(search.notify), search.threshold)  # 2 1 1500
```

```go title="alert_rules.go"
package main

import (
	"fmt"
	"maps"
	"slices"
)

type AlertRule struct {
	Name, Metric string
	Threshold    float64
	ForMinutes   int
	Labels       map[string]string
	Notify       []string
}

// Clone copies the struct by value, then copies the map and slice,
// which would otherwise be shared with the original.
func (r AlertRule) Clone() AlertRule {
	c := r
	c.Labels = maps.Clone(r.Labels)
	c.Notify = slices.Clone(r.Notify)
	return c
}

type RuleCatalog struct{ templates map[string]AlertRule }

func NewRuleCatalog() *RuleCatalog {
	return &RuleCatalog{templates: map[string]AlertRule{}}
}

func (c *RuleCatalog) Register(key string, r AlertRule) { c.templates[key] = r }

func (c *RuleCatalog) Create(key string) (AlertRule, error) {
	t, ok := c.templates[key]
	if !ok {
		return AlertRule{}, fmt.Errorf("unknown template: %s", key)
	}
	return t.Clone(), nil
}

func main() {
	catalog := NewRuleCatalog()
	catalog.Register("high-latency", AlertRule{
		Name: "high-latency", Metric: "http_p99_ms", Threshold: 800, ForMinutes: 5,
		Labels: map[string]string{"severity": "page", "team": "platform"},
		Notify: []string{"oncall:platform"},
	})

	checkout, _ := catalog.Create("high-latency")
	checkout.Name = "checkout-latency"
	checkout.Labels["service"] = "checkout"
	checkout.Notify = append(checkout.Notify, "chat:#checkout")

	search, _ := catalog.Create("high-latency")
	search.Name = "search-latency"
	search.Threshold = 1500

	fmt.Println(len(checkout.Labels), len(checkout.Notify))               // 3 2
	fmt.Println(len(search.Labels), len(search.Notify), search.Threshold) // 2 1 1500
}
```

```java title="AlertRules.java"
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AlertRules {
    interface Prototype<T> { T copy(); }

    static final class AlertRule implements Prototype<AlertRule> {
        String name;
        final String metric;
        double threshold;
        final int forMinutes;
        final Map<String, String> labels;
        final List<String> notify;

        AlertRule(String name, String metric, double threshold, int forMinutes,
                  Map<String, String> labels, List<String> notify) {
            this.name = name;
            this.metric = metric;
            this.threshold = threshold;
            this.forMinutes = forMinutes;
            this.labels = new HashMap<>(labels);
            this.notify = new ArrayList<>(notify);
        }

        // A copy constructor in disguise; avoids Cloneable and its shallow default.
        public AlertRule copy() {
            return new AlertRule(name, metric, threshold, forMinutes, labels, notify);
        }
    }

    static final class RuleCatalog {
        private final Map<String, AlertRule> templates = new HashMap<>();

        void register(String key, AlertRule rule) { templates.put(key, rule); }

        AlertRule create(String key) {
            AlertRule t = templates.get(key);
            if (t == null) throw new IllegalArgumentException("unknown template: " + key);
            return t.copy();
        }
    }

    public static void main(String[] args) {
        var catalog = new RuleCatalog();
        catalog.register("high-latency", new AlertRule("high-latency", "http_p99_ms", 800, 5,
            Map.of("severity", "page", "team", "platform"), List.of("oncall:platform")));

        AlertRule checkout = catalog.create("high-latency");
        checkout.name = "checkout-latency";
        checkout.labels.put("service", "checkout");
        checkout.notify.add("chat:#checkout");

        AlertRule search = catalog.create("high-latency");
        search.name = "search-latency";
        search.threshold = 1500;

        System.out.println(checkout.labels.size() + " " + checkout.notify.size()); // 3 2
        System.out.println(search.labels.size() + " " + search.notify.size() + " " + search.threshold); // 2 1 1500.0
    }
}
```

## Trade-offs

**Gains**
- New variants come from data (registered templates), not from new subclasses.
- Clients copy objects without knowing their concrete class or private fields.
- Expensive setup runs once per template, not once per instance.

**Costs**
- Correct deep copying is the whole job, and it is easy to get wrong: one forgotten collection and clones share state.
- Cyclic references, shared sub-objects and identity fields need explicit rules that generic copy helpers can't infer.
- Every new mutable field must be added to `clone()`. A test that mutates the copy is the only reliable guard.

## Relations

- [Factory Method](https://aipatterns.dev/design-patterns/factory-method.md) needs a creator subclass per product. Prototype replaces that hierarchy with registered instances.
- [Abstract Factory](https://aipatterns.dev/design-patterns/abstract-factory.md) can store one prototype per product kind and clone them, so a new family is new data rather than a new class.
- [Builder](https://aipatterns.dev/design-patterns/builder.md) assembles an object from nothing. A builder seeded from a prototype gives "like this one, but" construction with validation.
- [Composite](https://aipatterns.dev/design-patterns/composite.md) trees are often cloned as a whole, which forces a recursive deep copy through every child.
- [Memento](https://aipatterns.dev/design-patterns/memento.md) snapshots state to restore it later. A clone can serve as a simple memento when the object has no external resources.
- [Flyweight](https://aipatterns.dev/design-patterns/flyweight.md) is the opposite move: share one instance instead of copying it, which only works for immutable state.

## Agent checklist

- [ ] You checked the language's built-in copy facilities, and used them unless they copy too shallowly or too much.
- [ ] Every mutable collection and nested object is copied; immutable values and true shared services are not.
- [ ] Identity fields, locks, connections and handles are reset or excluded from the copy.
- [ ] A test mutates each collection on a clone and asserts the original and the registered template are unchanged.
- [ ] The registry returns clones only and never exposes the stored prototypes.
