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
Design Pattern Interview QA - 3
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.
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 itemQ6: 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:
- GoF catalog references: Refactoring Guru Pattern Catalog
- Pattern category overview and complete GoF list: Wikipedia - Design Patterns
- Python-oriented nuances and tradeoffs: Python Patterns Guide
What’s Next?
- Revisit Part 1: Design Pattern Interview QA - 1
- Revisit Part 2: Design Pattern Interview QA - 2
- Python architecture and API patterns: Python SWE Interview QA - 4