Design Patterns, Explained Simply · Creational Patterns: Creating Objects Flexibly

Prototype

Create new objects by copying a preconfigured one, and learn why the difference between a shallow and a deep copy decides whether the pattern helps or hurts.

The problem

A game spawns hundreds of enemies. Each "orc archer" starts with the same health, speed and a loadout of items, and setting one up takes a dozen lines (or an expensive load from a data file). Copy-pasting that setup everywhere is error-prone.

So the team keeps one configured orc as a template and copies it. Then a bug report arrives: when one orc picks up a rocket launcher, every orc has one. The copies were sharing the template's item list.

Real-world analogy

A cell divides by copying itself. Each new cell starts as an exact copy, then changes on its own. If two cells shared one nucleus, damage to one would damage both. Cloning only works when each copy gets its own insides.

Without the pattern

def make_orc_archer():
    orc = Enemy("orc archer", health=80, speed=3)
    orc.loadout = Loadout(["bow", "arrows"])
    # ...ten more lines of setup, repeated for every enemy type and every spawn point
    return orc

The pattern

  • Prototype: an object with a clone() method that returns a new, independent copy of itself.
  • Client: keeps one configured prototype per type (often in a registry) and calls clone() to make new objects, then tweaks the copy.
  • Shallow vs deep copy: a shallow copy copies the fields, but nested objects (lists, maps, other objects) are shared. A deep copy also copies the nested objects. clone() must copy every mutable part the copies will change independently.

The code

The first part shows the shallow-copy bug; the second uses a proper clone() that copies the loadout.

Cloning a template: the shared-loadout bug and the fixTry it yourself
class Loadout:
    def __init__(self, items):
        self.items = items


class Enemy:
    def __init__(self, kind, health, loadout):
        self.kind, self.health, self.loadout = kind, health, loadout

    def shallow_clone(self):  # copies fields only: the loadout object is shared
        return Enemy(self.kind, self.health, self.loadout)

    def clone(self):  # copies the nested loadout too
        return Enemy(self.kind, self.health, Loadout(list(self.loadout.items)))


template = Enemy("orc archer", 80, Loadout(["bow", "arrows"]))

a = template.shallow_clone()
a.loadout.items.append("rocket launcher")
print("shallow: template has", template.loadout.items)

template = Enemy("orc archer", 80, Loadout(["bow", "arrows"]))
b = template.clone()
b.loadout.items.append("rocket launcher")
b.health = 40
print("deep: template has", template.loadout.items, "health", template.health)
print("deep: copy has", b.loadout.items, "health", b.health)

Output

shallow: template has ['bow', 'arrows', 'rocket launcher']
deep: template has ['bow', 'arrows'] health 80
deep: copy has ['bow', 'arrows', 'rocket launcher'] health 40

The shallow copy leaked the rocket launcher back into the template. The proper clone() gave the copy its own loadout, so changes stay local.

When to use it

  • Creating an object from scratch is expensive or complicated (loaded from files, many setup steps), and you need many similar ones.
  • You want to create objects from configured examples chosen at runtime (a palette of shapes in a drawing app, enemy templates in a game).
  • You want to copy an object without knowing its concrete class (shape.clone() works for any shape).

When not to use it

  • Objects are cheap and simple to construct. A constructor or factory is clearer.
  • The object graph has cycles or external resources (open files, sockets, database connections). Deep-copying those correctly is hard, and often wrong.
  • Immutable objects never need cloning: share them freely, and "modify" by creating a new one with changes.

You've already used it

  • JavaScript: prototypal inheritance itself. Object.create(proto) makes objects from an existing one, and structuredClone() makes deep copies of plain data.
  • Python: copy.copy() (shallow) and copy.deepcopy() (deep), customisable with __copy__ and __deepcopy__.
  • PHP: the clone keyword and __clone(). Java: Cloneable and copy constructors (copy constructors are usually preferred).

Key points

  • Prototype creates objects by copying a configured instance with clone().
  • The critical detail is shallow vs deep copying: nested mutable objects must be copied, or clones share them.
  • Immutable objects make Prototype unnecessary and avoid the sharing bug entirely.
  • Further reading: Refactoring Guru: Prototype and Python's copy module.

Practice questions

What is the difference between a shallow copy and a deep copy, and why does it matter for Prototype?

Answer

A shallow copy copies an object's fields, but nested objects (lists, maps, other objects) are shared between the original and the copy. A deep copy also copies the nested objects. With a shallow clone, changing a nested list on the copy changes the prototype too, the classic Prototype bug.

When is the Prototype pattern useful?

Answer

When objects are expensive or complicated to create from scratch and you need many similar ones, or when you want to create objects from configured examples chosen at runtime, such as enemy templates or shapes in a palette.

How does PHP's `clone` keyword behave with object properties?

Answer

It makes a shallow copy: scalar and array properties are copied, but object properties still point to the same objects. Implement __clone() to clone nested objects that the copy must own.

Open in the app to mark lessons complete, take notes and track your progress.