Design Pattern Interview QA - 3

GoF Design Patterns Part 3: Prototype, Abstract Factory, Bridge, Composite, Iterator, Interpreter, Memento, and Visitor with Python examples.
Author
Published

27 May 2026

Keywords

prototype pattern, abstract factory pattern, bridge pattern, composite pattern, iterator pattern, interpreter pattern, memento pattern, visitor pattern, GoF design patterns, python design patterns

Introduction

This is Part 3 of our Design Pattern Interview QA series, focused on the remaining classic GoF patterns that are less frequently asked than Singleton/Factory/Observer but still appear in senior interviews and system design rounds.

For the first sets of patterns, see Design Pattern Interview QA - 1 and Design Pattern Interview QA - 2.


Q1: What is the Prototype Pattern?

Answer:

The Prototype pattern creates new objects by cloning existing ones instead of constructing from scratch. It is useful when object creation is expensive or when objects have many configuration fields.

graph TD
    linkStyle default stroke:#000,color:#000
    CLIENT["Client"] --> PROTO["Prototype Registry"]
    PROTO --> BASE_A["InvoiceTemplateA"]
    PROTO --> BASE_B["InvoiceTemplateB"]
    CLIENT -->|"clone(template_a)"| CLONE["New Invoice"]

    style PROTO fill:#8fe0bf,stroke:#333,color:#222
    style CLONE fill:#9fddea,stroke:#333,color:#222

from copy import deepcopy
from dataclasses import dataclass, field

@dataclass
class Invoice:
    title: str
    currency: str
    tax_rate: float
    line_items: list[dict] = field(default_factory=list)

    def clone(self) -> "Invoice":
        # Deep copy avoids shared mutable state.
        return deepcopy(self)

registry = {
    "saas_usd": Invoice("SaaS Invoice", "USD", 0.07),
    "consulting_eur": Invoice("Consulting Invoice", "EUR", 0.19),
}

invoice = registry["saas_usd"].clone()
invoice.line_items.append({"name": "Pro Plan", "price": 99})

Q2: What is the Abstract Factory Pattern (deep dive)?

Answer:

The Abstract Factory pattern provides an interface for creating related objects without specifying their concrete classes.

graph TD
    linkStyle default stroke:#000,color:#000
    CLIENT["Client"] --> FACTORY["UIFactory"]
    FACTORY --> LIGHT["LightFactory"]
    FACTORY --> DARK["DarkFactory"]
    LIGHT --> LB["LightButton"]
    LIGHT --> LI["LightInput"]
    DARK --> DB["DarkButton"]
    DARK --> DI["DarkInput"]

    style FACTORY fill:#8fe0bf,stroke:#333,color:#222
    style LIGHT fill:#ffe29a,stroke:#333,color:#222
    style DARK fill:#ff9f84,stroke:#333,color:#222

from abc import ABC, abstractmethod

class Button(ABC):
    @abstractmethod
    def render(self) -> str: ...

class Input(ABC):
    @abstractmethod
    def render(self) -> str: ...

class UIFactory(ABC):
    @abstractmethod
    def button(self) -> Button: ...

    @abstractmethod
    def input(self) -> Input: ...

class LightButton(Button):
    def render(self) -> str:
        return "<button class='light'>OK</button>"

class LightInput(Input):
    def render(self) -> str:
        return "<input class='light'/>"

class DarkButton(Button):
    def render(self) -> str:
        return "<button class='dark'>OK</button>"

class DarkInput(Input):
    def render(self) -> str:
        return "<input class='dark'/>"

class LightFactory(UIFactory):
    def button(self) -> Button:
        return LightButton()

    def input(self) -> Input:
        return LightInput()

class DarkFactory(UIFactory):
    def button(self) -> Button:
        return DarkButton()

    def input(self) -> Input:
        return DarkInput()

Q3: What is the Bridge Pattern?

Answer:

The Bridge pattern decouples abstraction from implementation so both can vary independently.

graph LR
    linkStyle default stroke:#000,color:#000
    ABSTRACTION["Notification"] --> IMPL["Sender"]
    ABSTRACTION --> EMAIL_NOTIF["AlertNotification"]
    IMPL --> SMTP["SmtpSender"]
    IMPL --> SLACK["SlackSender"]

    style ABSTRACTION fill:#8fe0bf,stroke:#333,color:#222
    style IMPL fill:#9fddea,stroke:#333,color:#222

from abc import ABC, abstractmethod

class Sender(ABC):
    @abstractmethod
    def send(self, title: str, body: str) -> None: ...

class SmtpSender(Sender):
    def send(self, title: str, body: str) -> None:
        print(f"SMTP => {title}: {body}")

class SlackSender(Sender):
    def send(self, title: str, body: str) -> None:
        print(f"Slack => {title}: {body}")

class Notification:
    def __init__(self, sender: Sender):
        self.sender = sender

class AlertNotification(Notification):
    def trigger(self, message: str) -> None:
        self.sender.send("ALERT", message)

Q4: What is the Composite Pattern?

Answer:

The Composite pattern lets clients treat individual objects and compositions uniformly.

graph TD
    linkStyle default stroke:#000,color:#000
    ROOT["Folder: root"] --> F1["Folder: docs"]
    ROOT --> F2["Folder: images"]
    F1 --> FILE1["File: readme.md"]
    F1 --> FILE2["File: guide.pdf"]

    style ROOT fill:#8fe0bf,stroke:#333,color:#222
    style F1 fill:#9fddea,stroke:#333,color:#222
    style F2 fill:#9fddea,stroke:#333,color:#222

from abc import ABC, abstractmethod

class Node(ABC):
    @abstractmethod
    def size(self) -> int: ...

class File(Node):
    def __init__(self, name: str, bytes_size: int):
        self.name = name
        self.bytes_size = bytes_size

    def size(self) -> int:
        return self.bytes_size

class Folder(Node):
    def __init__(self, name: str):
        self.name = name
        self.children: list[Node] = []

    def add(self, node: Node) -> None:
        self.children.append(node)

    def size(self) -> int:
        return sum(c.size() for c in self.children)

Q5: What is the Iterator Pattern?

Answer:

The Iterator pattern provides sequential access to elements without exposing internal representation.

graph LR
    linkStyle default stroke:#000,color:#000
    CLIENT["Client for-loop"] --> ITERABLE["OrderBook"]
    ITERABLE --> ITER["OrderIterator"]
    ITER --> O1["order_1"]
    ITER --> O2["order_2"]
    ITER --> O3["order_3"]

    style ITERABLE fill:#8fe0bf,stroke:#333,color:#222
    style ITER fill:#9fddea,stroke:#333,color:#222

class OrderBook:
    def __init__(self, orders: list[dict]):
        self._orders = orders

    def __iter__(self):
        return OrderIterator(self._orders)

class OrderIterator:
    def __init__(self, orders: list[dict]):
        self._orders = orders
        self._index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self._index >= len(self._orders):
            raise StopIteration
        item = self._orders[self._index]
        self._index += 1
        return item

Q6: What is the Interpreter Pattern?

Answer:

The Interpreter pattern defines grammar rules and interpreters for a simple language.

graph TD
    linkStyle default stroke:#000,color:#000
    INPUT["status:open AND priority:high"] --> PARSER["Parser"]
    PARSER --> AST["Expression Tree"]
    AST --> EVAL["Interpreter"]
    EVAL --> RESULT["Matching Tickets"]

    style PARSER fill:#8fe0bf,stroke:#333,color:#222
    style AST fill:#9fddea,stroke:#333,color:#222

from abc import ABC, abstractmethod

class Expr(ABC):
    @abstractmethod
    def interpret(self, ctx: dict) -> bool: ...

class Equals(Expr):
    def __init__(self, key: str, value: str):
        self.key = key
        self.value = value

    def interpret(self, ctx: dict) -> bool:
        return str(ctx.get(self.key)) == self.value

class And(Expr):
    def __init__(self, left: Expr, right: Expr):
        self.left = left
        self.right = right

    def interpret(self, ctx: dict) -> bool:
        return self.left.interpret(ctx) and self.right.interpret(ctx)

rule = And(Equals("status", "open"), Equals("priority", "high"))
assert rule.interpret({"status": "open", "priority": "high"})

Q7: What is the Memento Pattern?

Answer:

The Memento pattern captures and restores object state without exposing internals.

graph LR
    linkStyle default stroke:#000,color:#000
    ORIGIN["Editor"] -->|"create_memento()"| MEM["Memento"]
    CARE["History"] --> MEM
    CARE -->|"undo"| ORIGIN

    style ORIGIN fill:#8fe0bf,stroke:#333,color:#222
    style CARE fill:#9fddea,stroke:#333,color:#222

from dataclasses import dataclass

@dataclass(frozen=True)
class EditorMemento:
    text: str

class Editor:
    def __init__(self):
        self.text = ""

    def write(self, chunk: str) -> None:
        self.text += chunk

    def save(self) -> EditorMemento:
        return EditorMemento(self.text)

    def restore(self, memento: EditorMemento) -> None:
        self.text = memento.text

class History:
    def __init__(self):
        self.stack: list[EditorMemento] = []

    def push(self, m: EditorMemento) -> None:
        self.stack.append(m)

    def pop(self) -> EditorMemento:
        return self.stack.pop()

Q8: What is the Visitor Pattern?

Answer:

The Visitor pattern lets you add operations to object structures without modifying element classes.

graph TD
    linkStyle default stroke:#000,color:#000
    V["Visitor"] --> FILE["FileNode"]
    V --> FOLDER["FolderNode"]
    FILE -->|"accept(visitor)"| V
    FOLDER -->|"accept(visitor)"| V

    style V fill:#8fe0bf,stroke:#333,color:#222
    style FILE fill:#9fddea,stroke:#333,color:#222
    style FOLDER fill:#9fddea,stroke:#333,color:#222

from abc import ABC, abstractmethod

class Node(ABC):
    @abstractmethod
    def accept(self, visitor: "Visitor") -> None: ...

class FileNode(Node):
    def __init__(self, name: str, size: int):
        self.name = name
        self.size = size

    def accept(self, visitor: "Visitor") -> None:
        visitor.visit_file(self)

class FolderNode(Node):
    def __init__(self, name: str, children: list[Node]):
        self.name = name
        self.children = children

    def accept(self, visitor: "Visitor") -> None:
        visitor.visit_folder(self)

class Visitor(ABC):
    @abstractmethod
    def visit_file(self, file: FileNode) -> None: ...

    @abstractmethod
    def visit_folder(self, folder: FolderNode) -> None: ...

class SizeVisitor(Visitor):
    def __init__(self):
        self.total = 0

    def visit_file(self, file: FileNode) -> None:
        self.total += file.size

    def visit_folder(self, folder: FolderNode) -> None:
        for child in folder.children:
            child.accept(self)

Quick Comparison

Pattern Category Primary Purpose
Prototype Creational Clone configured objects quickly
Abstract Factory Creational Build related object families
Bridge Structural Separate abstraction from implementation
Composite Structural Treat leaf and tree nodes uniformly
Iterator Behavioral Traverse collections safely
Interpreter Behavioral Evaluate domain-specific rules
Memento Behavioral Snapshot and restore state
Visitor Behavioral Add operations without changing node classes

Research Notes

This article structure and pattern coverage were cross-checked with:


What’s Next?