Back to blog

Prototype Pattern Explained

oopcreational-patternsdesign-patternstypescriptpythonjava
Prototype Pattern Explained

Introduction

Some objects are expensive to create — they require database queries, complex configuration, or a series of calculations to get into a valid state. Once you have one good instance, why throw it away and build another from scratch?

The Prototype Pattern answers this question: instead of creating a new object, clone an existing one and modify only what's different. It's deceptively simple and surprisingly powerful.

What You'll Learn

✅ Understand the problem the Prototype pattern solves
✅ Implement shallow copy vs deep copy correctly
✅ Build a Prototype Registry for managed cloning
✅ Apply the pattern in TypeScript, Python, and Java
✅ Recognize Prototype in the wild (JavaScript's prototypal inheritance, game engines)
✅ Know when to use Prototype vs other creational patterns

Prerequisites


The Problem: Expensive Object Creation

Consider a game with enemy units. Each Enemy has stats, AI behavior trees, textures, pathfinding graphs, and sound profiles — all loaded from disk and computed at initialization:

class Enemy {
  stats: Stats;
  behaviorTree: BehaviorTree;
  textures: TextureSet;
  pathfindingGraph: Graph;
 
  constructor(config: EnemyConfig) {
    this.stats = loadStatsFromDisk(config.statFile);      // slow
    this.behaviorTree = parseBehaviorXML(config.aiFile);  // slow
    this.textures = loadTextures(config.textureDir);      // very slow
    this.pathfindingGraph = computeGraph(config.mapData); // CPU-intensive
  }
}
 
// Spawning 50 enemies per wave = 50 × (all that expensive initialization)
for (let i = 0; i < 50; i++) {
  enemies.push(new Enemy(goblinConfig)); // ❌ painfully slow
}

The solution: load one Enemy once, then clone it 49 more times:

const template = new Enemy(goblinConfig); // ✅ load once
for (let i = 0; i < 50; i++) {
  const enemy = template.clone(); // fast copy
  enemy.position = randomSpawnPoint();
  enemies.push(enemy);
}

This is the Prototype pattern in one sentence: create new objects by copying a prototype.


Pattern Structure

The Prototype pattern has a minimal structure:

Key participants:

  • Prototype — interface declaring the clone() method
  • ConcretePrototype — implements clone(), knows how to copy itself
  • Client — calls clone() instead of calling new

The pattern delegates the copying responsibility to the object itself — it knows its own internal state best.


Shallow Copy vs Deep Copy

This is the most critical concept in the Prototype pattern. Get it wrong and you'll have objects sharing state in ways that cause subtle, hard-to-debug bugs.

Shallow Copy

A shallow copy duplicates the object's top-level fields. References (pointers) to nested objects are copied — not the nested objects themselves. Both the original and the clone point to the same nested objects.

class Address {
  constructor(
    public street: string,
    public city: string
  ) {}
}
 
class User {
  constructor(
    public name: string,
    public address: Address
  ) {}
 
  shallowClone(): User {
    return Object.assign(new User("", new Address("", "")), this);
    // Or simply: return { ...this } as User
  }
}
 
const original = new User("Alice", new Address("123 Main St", "Springfield"));
const clone = original.shallowClone();
 
clone.name = "Bob";           // ✅ independent — primitive value
clone.address.city = "Shelbyville"; // ❌ modifies original.address.city too!
 
console.log(original.address.city); // "Shelbyville" — oops!

Visualizing shallow copy:

Deep Copy

A deep copy recursively duplicates all nested objects. The clone is completely independent.

class Address {
  constructor(
    public street: string,
    public city: string
  ) {}
 
  clone(): Address {
    return new Address(this.street, this.city);
  }
}
 
class User {
  constructor(
    public name: string,
    public address: Address
  ) {}
 
  deepClone(): User {
    return new User(this.name, this.address.clone()); // clone nested objects too
  }
}
 
const original = new User("Alice", new Address("123 Main St", "Springfield"));
const clone = original.deepClone();
 
clone.name = "Bob";
clone.address.city = "Shelbyville";
 
console.log(original.address.city); // "Springfield" ✅ original untouched

When to Use Each

ScenarioUse
All fields are primitives (strings, numbers, booleans)Shallow copy is safe
Fields are references to immutable objectsShallow copy is safe
Fields are references to mutable objectsDeep copy required
Nested objects contain nested objectsDeep copy required
Performance is critical and mutation won't happenShallow copy for speed

TypeScript Implementation

Basic Prototype with Interface

interface Cloneable<T> {
  clone(): T;
}
 
class GameCharacter implements Cloneable<GameCharacter> {
  constructor(
    public name: string,
    public health: number,
    public attack: number,
    public skills: string[],   // mutable array — needs deep copy
    public position: { x: number; y: number }  // mutable object — needs deep copy
  ) {}
 
  clone(): GameCharacter {
    return new GameCharacter(
      this.name,
      this.health,
      this.attack,
      [...this.skills],            // deep copy array
      { ...this.position }         // deep copy object
    );
  }
}
 
// Usage
const goblinTemplate = new GameCharacter(
  "Goblin",
  100,
  15,
  ["slash", "dodge"],
  { x: 0, y: 0 }
);
 
const goblin1 = goblinTemplate.clone();
goblin1.position = { x: 10, y: 20 };
goblin1.health = 80; // wounded
 
const goblin2 = goblinTemplate.clone();
goblin2.position = { x: 50, y: 30 };
 
// Template unchanged
console.log(goblinTemplate.health);  // 100
console.log(goblinTemplate.position); // { x: 0, y: 0 }

Prototype Registry

A Prototype Registry (also called a Prototype Store or Cache) maintains a collection of pre-built prototypes keyed by name. Clients request clones by key:

class CharacterRegistry {
  private prototypes = new Map<string, GameCharacter>();
 
  register(key: string, prototype: GameCharacter): void {
    this.prototypes.set(key, prototype);
  }
 
  create(key: string): GameCharacter {
    const prototype = this.prototypes.get(key);
    if (!prototype) {
      throw new Error(`Prototype '${key}' not found in registry`);
    }
    return prototype.clone();
  }
}
 
// Setup registry once (e.g., at game load)
const registry = new CharacterRegistry();
 
registry.register("goblin", new GameCharacter("Goblin", 100, 15, ["slash"], { x: 0, y: 0 }));
registry.register("troll", new GameCharacter("Troll", 500, 40, ["smash", "roar"], { x: 0, y: 0 }));
registry.register("archer", new GameCharacter("Archer", 80, 25, ["shoot", "dodge"], { x: 0, y: 0 }));
 
// Spawn enemies efficiently
function spawnWave(type: string, count: number, spawnPoints: { x: number; y: number }[]) {
  return spawnPoints.slice(0, count).map((pos, i) => {
    const enemy = registry.create(type);
    enemy.position = pos;
    enemy.name = `${enemy.name} #${i + 1}`;
    return enemy;
  });
}
 
const wave = spawnWave("goblin", 10, generateSpawnPoints());

Document Template Example

A common real-world use case: email/document templates.

interface Section {
  title: string;
  content: string;
  clone(): Section;
}
 
class TextSection implements Section {
  constructor(
    public title: string,
    public content: string
  ) {}
 
  clone(): TextSection {
    return new TextSection(this.title, this.content);
  }
}
 
class Document {
  constructor(
    public title: string,
    public author: string,
    public sections: Section[],
    public metadata: Record<string, string>
  ) {}
 
  clone(): Document {
    return new Document(
      this.title,
      this.author,
      this.sections.map(s => s.clone()),     // deep clone sections
      { ...this.metadata }                    // shallow clone (values are strings)
    );
  }
}
 
// Create a template document
const reportTemplate = new Document(
  "Monthly Report",
  "Template",
  [
    new TextSection("Executive Summary", "Insert summary here..."),
    new TextSection("Key Metrics", "Insert metrics here..."),
    new TextSection("Conclusion", "Insert conclusion here..."),
  ],
  { company: "Acme Corp", confidential: "true" }
);
 
// Generate this month's report from the template
const janReport = reportTemplate.clone();
janReport.title = "Monthly Report - January 2026";
janReport.author = "Alice Johnson";
janReport.sections[0].content = "January was a record-breaking month...";
janReport.metadata.period = "January 2026";
 
// Template remains pristine for next month
console.log(reportTemplate.title);  // "Monthly Report" — unchanged

Python Implementation

Python provides built-in support for shallow and deep copying via the copy module.

import copy
from dataclasses import dataclass, field
from typing import Protocol
 
class Cloneable(Protocol):
    def clone(self) -> "Cloneable":
        ...
 
@dataclass
class Skill:
    name: str
    damage: int
    cooldown: float
 
    def clone(self) -> "Skill":
        return Skill(self.name, self.damage, self.cooldown)
 
@dataclass
class GameCharacter:
    name: str
    health: int
    attack: int
    skills: list[Skill] = field(default_factory=list)
    position: dict = field(default_factory=lambda: {"x": 0, "y": 0})
 
    def shallow_clone(self) -> "GameCharacter":
        """Shallow copy — skills list is shared!"""
        return copy.copy(self)
 
    def clone(self) -> "GameCharacter":
        """Deep copy — fully independent"""
        return copy.deepcopy(self)
 
    def manual_clone(self) -> "GameCharacter":
        """Manual deep clone — more control over what's copied"""
        return GameCharacter(
            name=self.name,
            health=self.health,
            attack=self.attack,
            skills=[skill.clone() for skill in self.skills],
            position=dict(self.position)
        )
 
# Shallow copy pitfall
goblin_template = GameCharacter(
    name="Goblin",
    health=100,
    attack=15,
    skills=[Skill("slash", 20, 1.5), Skill("dodge", 0, 3.0)]
)
 
shallow = goblin_template.shallow_clone()
shallow.skills.append(Skill("backstab", 35, 5.0))
 
print(len(goblin_template.skills))  # 3 — template was modified! Bug!
 
# Deep copy — safe
goblin_template2 = GameCharacter(
    name="Goblin",
    health=100,
    attack=15,
    skills=[Skill("slash", 20, 1.5), Skill("dodge", 0, 3.0)]
)
deep = goblin_template2.clone()
deep.skills.append(Skill("backstab", 35, 5.0))
 
print(len(goblin_template2.skills))  # 2 — template untouched ✅

Python Prototype Registry

from typing import Dict, TypeVar
 
T = TypeVar("T", bound=GameCharacter)
 
class CharacterRegistry:
    def __init__(self):
        self._prototypes: Dict[str, GameCharacter] = {}
 
    def register(self, key: str, prototype: GameCharacter) -> None:
        self._prototypes[key] = prototype
 
    def create(self, key: str, **overrides) -> GameCharacter:
        if key not in self._prototypes:
            raise KeyError(f"Prototype '{key}' not registered")
        char = self._prototypes[key].clone()
        for attr, value in overrides.items():
            setattr(char, attr, value)
        return char
 
# Usage
registry = CharacterRegistry()
registry.register("goblin", GameCharacter("Goblin", 100, 15, [Skill("slash", 20, 1.5)]))
registry.register("elite_goblin", GameCharacter("Elite Goblin", 200, 30, [Skill("slash", 30, 1.0), Skill("shield", 0, 4.0)]))
 
# Spawn with customization
g1 = registry.create("goblin", name="Goblin Scout", position={"x": 10, "y": 5})
g2 = registry.create("goblin", health=50)  # weakened variant
 
print(g1.name)    # Goblin Scout
print(g2.health)  # 50

Using __copy__ and __deepcopy__ Hooks

Python lets you customize copy behavior with special methods:

import copy
 
class Configuration:
    def __init__(self, settings: dict, cache: dict = None):
        self.settings = settings
        self.cache = cache or {}  # cache should NOT be copied
 
    def __copy__(self):
        """Shallow copy: share the cache"""
        new_obj = Configuration.__new__(Configuration)
        new_obj.__dict__.update(self.__dict__)
        return new_obj
 
    def __deepcopy__(self, memo):
        """Deep copy: fresh cache, deep copy settings, shared heavy resources"""
        new_obj = Configuration.__new__(Configuration)
        memo[id(self)] = new_obj
        new_obj.settings = copy.deepcopy(self.settings, memo)
        new_obj.cache = {}  # don't copy the cache — intentionally fresh
        return new_obj
 
config = Configuration({"theme": "dark", "language": "en"})
config.cache["user_prefs"] = {"font_size": 14}
 
cloned = copy.deepcopy(config)
cloned.settings["theme"] = "light"
 
print(config.settings["theme"])  # "dark" — original untouched ✅
print(len(cloned.cache))         # 0 — cache not copied as intended ✅

Java Implementation

Java has a built-in Cloneable interface and Object.clone() method, though they come with caveats.

Using Cloneable (Classic Approach)

import java.util.ArrayList;
import java.util.List;
 
public class GameCharacter implements Cloneable {
    private String name;
    private int health;
    private int attack;
    private List<String> skills;  // mutable — needs deep copy
 
    public GameCharacter(String name, int health, int attack, List<String> skills) {
        this.name = name;
        this.health = health;
        this.attack = attack;
        this.skills = skills;
    }
 
    @Override
    public GameCharacter clone() {
        try {
            GameCharacter clone = (GameCharacter) super.clone(); // shallow copy of primitives
            clone.skills = new ArrayList<>(this.skills);         // deep copy of list
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("Should never happen", e);
        }
    }
 
    // Getters/setters omitted for brevity
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getHealth() { return health; }
    public void setHealth(int health) { this.health = health; }
    public List<String> getSkills() { return skills; }
}
 
// Usage
GameCharacter goblinTemplate = new GameCharacter(
    "Goblin", 100, 15,
    new ArrayList<>(List.of("slash", "dodge"))
);
 
GameCharacter goblin1 = goblinTemplate.clone();
goblin1.setName("Goblin Scout");
goblin1.getSkills().add("backstab");
 
// Template unaffected
System.out.println(goblinTemplate.getName());         // "Goblin"
System.out.println(goblinTemplate.getSkills().size()); // 2

Note: Java's Cloneable is widely considered a poorly designed interface. It has no clone() method itself — it merely signals to Object.clone() that cloning is allowed. The copy constructor pattern below is often preferred in modern Java.

Copy Constructor Pattern (Preferred in Modern Java)

public class GameCharacter {
    private final String name;
    private final int health;
    private final int attack;
    private final List<String> skills;
 
    // Regular constructor
    public GameCharacter(String name, int health, int attack, List<String> skills) {
        this.name = name;
        this.health = health;
        this.attack = attack;
        this.skills = List.copyOf(skills); // immutable copy
    }
 
    // Copy constructor — explicit and clear
    public GameCharacter(GameCharacter other) {
        this(other.name, other.health, other.attack, other.skills);
    }
 
    // Builder-style modification — returns new instance
    public GameCharacter withName(String name) {
        return new GameCharacter(name, this.health, this.attack, this.skills);
    }
 
    public GameCharacter withHealth(int health) {
        return new GameCharacter(this.name, health, this.attack, this.skills);
    }
 
    public GameCharacter addSkill(String skill) {
        List<String> newSkills = new ArrayList<>(this.skills);
        newSkills.add(skill);
        return new GameCharacter(this.name, this.health, this.attack, newSkills);
    }
 
    public String getName() { return name; }
    public int getHealth() { return health; }
    public List<String> getSkills() { return skills; }
}
 
// Usage — immutable + copy constructor = clean cloning
GameCharacter goblinTemplate = new GameCharacter(
    "Goblin", 100, 15, List.of("slash", "dodge")
);
 
GameCharacter scout = new GameCharacter(goblinTemplate)
    .withName("Goblin Scout")
    .addSkill("backstab");
 
GameCharacter weakened = new GameCharacter(goblinTemplate)
    .withHealth(50);
 
System.out.println(goblinTemplate.getHealth());  // 100 ✅
System.out.println(scout.getSkills());           // [slash, dodge, backstab]
System.out.println(weakened.getHealth());         // 50

Java Prototype Registry

import java.util.HashMap;
import java.util.Map;
 
public class CharacterRegistry {
    private final Map<String, GameCharacter> prototypes = new HashMap<>();
 
    public void register(String key, GameCharacter prototype) {
        prototypes.put(key, prototype);
    }
 
    public GameCharacter create(String key) {
        GameCharacter prototype = prototypes.get(key);
        if (prototype == null) {
            throw new IllegalArgumentException("Prototype not found: " + key);
        }
        return new GameCharacter(prototype); // copy constructor
    }
}
 
public class Game {
    public static void main(String[] args) {
        CharacterRegistry registry = new CharacterRegistry();
 
        registry.register("goblin",
            new GameCharacter("Goblin", 100, 15, List.of("slash")));
        registry.register("troll",
            new GameCharacter("Troll", 500, 40, List.of("smash", "roar")));
 
        // Spawn wave
        List<GameCharacter> wave = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            wave.add(registry.create("goblin"));
        }
        for (int i = 0; i < 5; i++) {
            wave.add(registry.create("troll"));
        }
 
        System.out.println("Wave spawned: " + wave.size() + " enemies");
    }
}

JavaScript's Prototypal Inheritance

JavaScript is unique: it uses Prototype at the language level. Every object has a [[Prototype]] link to another object. This is not the GoF Prototype pattern — it's a different concept with the same name.

// JavaScript's built-in prototypal inheritance
const animalProto = {
  speak() {
    return `${this.name} says ${this.sound}`;
  },
  eat() {
    return `${this.name} is eating`;
  }
};
 
// Object.create() implements prototypal inheritance
const dog = Object.create(animalProto);
dog.name = "Rex";
dog.sound = "Woof";
 
console.log(dog.speak()); // "Rex says Woof"
console.log(Object.getPrototypeOf(dog) === animalProto); // true
 
// Modern classes are syntactic sugar over this prototype chain
class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }
  speak() {
    return `${this.name} says ${this.sound}`;
  }
}
 
class Dog extends Animal {
  constructor(name) {
    super(name, "Woof");
  }
  fetch() { return `${this.name} fetches the ball!`; }
}
 
// Under the hood, Dog.prototype is linked to Animal.prototype
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true

GoF Prototype Pattern in JavaScript

To implement the GoF pattern (cloning) in JavaScript:

// Using structuredClone (Node 17+, modern browsers) — deep clone
const template = {
  type: "enemy",
  stats: { health: 100, attack: 15 },
  skills: ["slash", "dodge"],
  clone() {
    return structuredClone(this);
  }
};
 
const enemy1 = template.clone();
enemy1.stats.health = 80; // wounded
 
console.log(template.stats.health); // 100 ✅ — structuredClone is a true deep copy
 
// Older approach: JSON round-trip (works for plain data, loses methods)
const deepCopy = JSON.parse(JSON.stringify(template));

Real-World Use Cases

1. Configuration Presets

class ServerConfig {
  constructor(
    public host: string,
    public port: number,
    public timeout: number,
    public retries: number,
    public headers: Record<string, string>,
    public features: string[]
  ) {}
 
  clone(): ServerConfig {
    return new ServerConfig(
      this.host,
      this.port,
      this.timeout,
      this.retries,
      { ...this.headers },
      [...this.features]
    );
  }
 
  withTimeout(timeout: number): ServerConfig {
    const clone = this.clone();
    clone.timeout = timeout;
    return clone;
  }
 
  withFeature(feature: string): ServerConfig {
    const clone = this.clone();
    clone.features.push(feature);
    return clone;
  }
}
 
// Base config
const baseConfig = new ServerConfig(
  "api.example.com", 443, 5000, 3,
  { "Content-Type": "application/json" },
  ["auth", "logging"]
);
 
// Derived configs
const fastConfig = baseConfig.withTimeout(1000);
const debugConfig = baseConfig.withFeature("verbose-logging").withFeature("request-tracing");
const productionConfig = baseConfig.withTimeout(3000).withFeature("rate-limiting");

2. UI Component Templates

interface ComponentConfig {
  styles: Record<string, string>;
  classes: string[];
  attributes: Record<string, string>;
  clone(): ComponentConfig;
}
 
class ButtonConfig implements ComponentConfig {
  constructor(
    public styles: Record<string, string>,
    public classes: string[],
    public attributes: Record<string, string>
  ) {}
 
  clone(): ButtonConfig {
    return new ButtonConfig(
      { ...this.styles },
      [...this.classes],
      { ...this.attributes }
    );
  }
}
 
// Design system base components
const primaryButton = new ButtonConfig(
  { background: "#3b82f6", color: "white", padding: "8px 16px" },
  ["btn", "btn-primary"],
  { type: "button", role: "button" }
);
 
// Variants from prototype
const dangerButton = primaryButton.clone();
dangerButton.styles.background = "#ef4444";
dangerButton.classes.push("btn-danger");
 
const disabledButton = primaryButton.clone();
disabledButton.styles.opacity = "0.5";
disabledButton.attributes.disabled = "true";
disabledButton.attributes["aria-disabled"] = "true";

When to Use the Prototype Pattern

Use Prototype when:

  • Object creation is expensive (DB queries, network calls, file I/O, heavy computation)
  • You need many similar objects that differ only in a few fields
  • The class to instantiate is specified at runtime (via registry)
  • You want to avoid building complex class hierarchies of factories
  • The object has many optional fields and you want configuration presets

Avoid Prototype when:

  • Objects are cheap to create — just use new
  • All fields must be set fresh — cloning provides no benefit
  • Deep copy is complex or has side effects (database connections, file handles, threads)
  • You need a completely different type, not a copy of an existing one

Prototype vs Other Creational Patterns

PatternCreates Object ViaBest For
Factory MethodSubclass decidesVarying types of products
Abstract FactoryFamily of factoriesRelated product families
BuilderStep-by-step constructionComplex objects with many optional parts
PrototypeCloning existing instanceCopies of expensive-to-create objects
SingletonSingle shared instanceExactly one instance needed

Prototype and Builder are sometimes combined: build one prototype with Builder, then clone it repeatedly.


Common Pitfalls

1. Accidental Shallow Copy

// ❌ Bug: arrays and objects are shared
class Config {
  tags: string[] = [];
 
  clone(): Config {
    return Object.assign(new Config(), this); // shallow!
  }
}
 
const base = new Config();
base.tags.push("prod");
 
const clone = base.clone();
clone.tags.push("debug"); // modifies base.tags too!
 
console.log(base.tags); // ["prod", "debug"] — oops
// ✅ Fix: deep copy all mutable references
class Config {
  tags: string[] = [];
 
  clone(): Config {
    const c = new Config();
    c.tags = [...this.tags]; // copy the array
    return c;
  }
}

2. Circular References in Deep Copy

class Node {
  next: Node | null = null;
  prev: Node | null = null; // circular!
 
  deepClone(): Node {
    // Naive deepClone would cause infinite recursion
    // Use a Map to track visited nodes
    const visited = new Map<Node, Node>();
 
    function cloneNode(node: Node | null): Node | null {
      if (!node) return null;
      if (visited.has(node)) return visited.get(node)!;
 
      const cloned = new Node();
      visited.set(node, cloned);
      cloned.next = cloneNode(node.next);
      cloned.prev = cloneNode(node.prev);
      return cloned;
    }
 
    return cloneNode(this)!;
  }
}

3. Cloning Objects with Non-Cloneable Resources

class DatabaseConnection {
  private connection: pg.Client; // cannot be cloned!
 
  constructor(config: DBConfig) {
    this.connection = new pg.Client(config);
  }
 
  // ❌ Don't clone this — clone the config, create a new connection
  clone(): DatabaseConnection {
    // This would share the same underlying socket — dangerous
    return Object.assign(new DatabaseConnection({} as DBConfig), this);
  }
}
 
// ✅ Better: prototype stores config, not the live connection
class DBConnectionFactory {
  constructor(private config: DBConfig) {}
 
  create(): pg.Client {
    return new pg.Client({ ...this.config }); // fresh connection every time
  }
}

Summary

The Prototype pattern is the most straightforward creational pattern — clone instead of construct. Its power lies in:

  1. Performance: Avoid expensive initialization by copying a pre-built prototype
  2. Flexibility: Modify clones freely without affecting the original
  3. Simplicity: Registry + clone is often simpler than factories with complex hierarchies

Key takeaways:

✅ Always distinguish shallow copy from deep copy — most bugs come from accidentally sharing mutable references
✅ Use a Prototype Registry to manage and retrieve named prototypes by key
✅ In Python, prefer copy.deepcopy() or implement __deepcopy__ for full control
✅ In Java, prefer copy constructors over Cloneable (which is widely considered broken)
✅ In JavaScript, structuredClone() is the modern, correct way to deep clone
✅ Watch out for circular references and non-cloneable resources (file handles, DB connections)


What's Next

Up next in the OOP & Design Patterns series:

  • OOP-12: Adapter and Facade Patterns — structural patterns for incompatible interfaces
  • OOP-13: Decorator and Proxy Patterns — wrapping objects to extend or control behavior
  • OOP-14: Strategy and Template Method — behavioral patterns for algorithms

Practice Exercises

  1. Game Spawner: Build a MonsterRegistry that holds 5 different monster prototypes (each with health, attack, skills, and loot tables). Implement a spawnWave(type, count) function that clones and positions them on a 100×100 grid.

  2. Email Template Engine: Create a EmailTemplate class with subject, body (with {{placeholder}} variables), and recipient lists. Implement clone() and a fill(data: Record<string, string>) method that returns a new email with placeholders replaced.

  3. Config Builder: Implement a HttpClientConfig with base URL, timeout, retry count, and default headers. Build three preset configs (fast, standard, resilient) derived from a base prototype, each with different timeout/retry values.

  4. Circular List Clone: Implement a doubly linked circular list (head.prev = tail, tail.next = head) and write a deepClone() that correctly handles the circular references without infinite recursion.

📬 Subscribe to Newsletter

Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.

We respect your privacy. Unsubscribe at any time.

💬 Comments

Sign in to leave a comment

We'll never post without your permission.