Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Visitor Pattern

Add new operations without modifying classes - separate algorithms from the objects they operate on!

Visitor Pattern: Operations on Object Structures

Section titled “Visitor Pattern: Operations on Object Structures”

Now let’s dive into the Visitor Pattern - one of the most sophisticated behavioral design patterns that lets you define new operations on objects without changing their classes. It separates algorithms from the object structure they operate on.

Imagine a document editor with different elements - paragraphs, images, tables. You need multiple operations: export to PDF, export to HTML, calculate word count, spell check. Without Visitor, you’d add methods to each element for each operation. With Visitor Pattern, you create separate visitor classes for each operation, keeping elements clean!

The Visitor Pattern lets you add new operations to a class hierarchy without modifying the classes. You define a visitor object that implements all variations of an operation for each class in the hierarchy, and the elements “accept” visitors to let them perform operations.

The Visitor Pattern is useful when:

  1. You need many unrelated operations - On objects in a structure
  2. Object structure rarely changes - But operations change often
  3. You want to avoid polluting classes - Keep operations separate
  4. You need operations across class hierarchy - Different handling per type
  5. You want to accumulate state - Visitor can gather information as it visits

What Happens If We Don’t Use Visitor Pattern?

Section titled “What Happens If We Don’t Use Visitor Pattern?”

Without the Visitor Pattern, you might:


Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Visitor Pattern achieves double dispatch:

sequenceDiagram
    participant Client
    participant Circle
    participant AreaCalculator
    
    Client->>Circle: accept(areaCalculator)
    activate Circle
    Note right of Circle: First dispatch:\nbased on Circle type
    
    Circle->>AreaCalculator: visit_circle(this)
    activate AreaCalculator
    Note right of AreaCalculator: Second dispatch:\nbased on visitor type
    
    AreaCalculator->>AreaCalculator: Calculate π × r²
    AreaCalculator-->>Circle: return area
    deactivate AreaCalculator
    
    Circle-->>Client: return result
    deactivate Circle
    
    Note over Client,AreaCalculator: Double dispatch allows different<br/>behavior based on BOTH types!

You’re building a graphics application with different shapes. You need to calculate area, perimeter, and draw operations. Without Visitor Pattern:

Problems:

classDiagram
    class Shape {
        <<interface>>
        +accept(visitor) void
    }
    class Circle {
        -radius: float
        +accept(visitor) void
        +get_radius() float
    }
    class Rectangle {
        -width: float
        -height: float
        +accept(visitor) void
        +get_width() float
        +get_height() float
    }
    class ShapeVisitor {
        <<interface>>
        +visit_circle(circle) void
        +visit_rectangle(rect) void
    }
    class AreaCalculator {
        -total_area: float
        +visit_circle(circle) void
        +visit_rectangle(rect) void
        +get_total() float
    }
    class PerimeterCalculator {
        -total_perimeter: float
        +visit_circle(circle) void
        +visit_rectangle(rect) void
        +get_total() float
    }
    class SVGExporter {
        -svg_elements: List
        +visit_circle(circle) void
        +visit_rectangle(rect) void
        +get_svg() str
    }
    
    Shape <|.. Circle : implements
    Shape <|.. Rectangle : implements
    ShapeVisitor <|.. AreaCalculator : implements
    ShapeVisitor <|.. PerimeterCalculator : implements
    ShapeVisitor <|.. SVGExporter : implements
    Shape --> ShapeVisitor : accepts
    
    note for Shape "Elements accept visitors"
    note for ShapeVisitor "Visitors define operations"

Real-World Software Example: Document Exporter

Section titled “Real-World Software Example: Document Exporter”

Now let’s see a realistic software example - a document system that needs to export to multiple formats.

You’re building a document editor with different elements (headings, paragraphs, code blocks, images). You need to export to HTML, Markdown, PDF, and plain text. Without Visitor Pattern:

Problems:

  • Export methods scattered across all element classes
  • Adding new format requires modifying ALL classes
  • Related code (all HTML export) spread across files
  • Elements have too many responsibilities
classDiagram
    class DocumentElement {
        <<interface>>
        +accept(visitor) void
    }
    class Heading {
        -text: str
        -level: int
        +accept(visitor) void
    }
    class Paragraph {
        -text: str
        +accept(visitor) void
    }
    class CodeBlock {
        -code: str
        -language: str
        +accept(visitor) void
    }
    class DocumentVisitor {
        <<interface>>
        +visit_heading(heading) void
        +visit_paragraph(para) void
        +visit_code_block(code) void
    }
    class HTMLExporter {
        +visit_heading(heading) void
        +visit_paragraph(para) void
        +visit_code_block(code) void
        +get_output() str
    }
    class MarkdownExporter {
        +visit_heading(heading) void
        +visit_paragraph(para) void
        +visit_code_block(code) void
        +get_output() str
    }
    
    DocumentElement <|.. Heading : implements
    DocumentElement <|.. Paragraph : implements
    DocumentElement <|.. CodeBlock : implements
    DocumentVisitor <|.. HTMLExporter : implements
    DocumentVisitor <|.. MarkdownExporter : implements
    DocumentElement --> DocumentVisitor : accepts

Visitor Pattern uses double dispatch to determine which code to run:

graph LR
    A[element.accept visitor] -->|1st dispatch| B[concrete element type]
    B -->|2nd dispatch| C[visitor.visit_X element]
    C --> D[specific operation]
    
    style A fill:#f9f,stroke:#333
    style D fill:#9f9,stroke:#333
  1. First dispatch: Based on element type (Circle, Rectangle)
  2. Second dispatch: Based on visitor type (AreaCalculator, SVGExporter)

This allows choosing behavior based on BOTH types at runtime!


Mistake 1: Visitor Knows Too Much About Elements

Section titled “Mistake 1: Visitor Knows Too Much About Elements”

Mistake 2: Forgetting to Add Visit Methods for New Elements

Section titled “Mistake 2: Forgetting to Add Visit Methods for New Elements”

Mistake 3: Using Visitor When Structure Changes Often

Section titled “Mistake 3: Using Visitor When Structure Changes Often”

  1. Open/Closed for Operations - Add operations without modifying elements
  2. Single Responsibility - Each visitor has one operation
  3. Related Code Together - All export logic in one class
  4. Accumulate State - Visitors can gather info across visits
  5. Double Dispatch - Operation based on both types
  6. Clean Elements - Elements don’t have operation logic

Visitor Pattern is a behavioral design pattern that lets you add new operations to objects without modifying them. It separates algorithms from the object structure they operate on.

  • Add operations - Without modifying element classes
  • Single Responsibility - Each visitor = one operation
  • Related code together - All HTML export in one class
  • Accumulate state - Gather info across visits
  • Double dispatch - Choose based on both types
  1. Define Visitor interface - Visit method per element type
  2. Define Element interface - accept(visitor) method
  3. Create Concrete Elements - Call visitor.visit_X(this)
  4. Create Concrete Visitors - Implement operation per type
  5. Client - Creates visitor, passes to elements
Element.accept(visitor) → visitor.visit_X(element)
  • Visitor - Interface with visit methods
  • Concrete Visitor - Implements specific operation
  • Element - Interface with accept method
  • Concrete Element - Calls appropriate visit method
class ShapeVisitor(ABC):
@abstractmethod
def visit_circle(self, circle): pass
@abstractmethod
def visit_rectangle(self, rect): pass
class Shape(ABC):
@abstractmethod
def accept(self, visitor): pass
class Circle(Shape):
def accept(self, visitor):
visitor.visit_circle(self) # Double dispatch!
class AreaCalculator(ShapeVisitor):
def visit_circle(self, circle):
return 3.14 * circle.radius ** 2

✅ Object structure is stable
✅ Operations change often
✅ Need type-specific operations
✅ Need to accumulate state

❌ Element types change often
❌ Few simple operations
❌ Don’t need type-specific handling

  • Visitor Pattern = Operations on object structures
  • Double Dispatch = Choose by both types
  • Visitor = One operation, many element types
  • Element = Accepts visitors
  • Trade-off = Easy to add operations, hard to add elements

What to say:

“Visitor Pattern is a behavioral design pattern that separates algorithms from the objects they operate on. It lets you add new operations to existing class hierarchies without modifying them. The key mechanism is double dispatch - the operation executed depends on both the element type AND the visitor type.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of double dispatch
  • Indicates you understand the trade-offs

Must explain:

  • Single dispatch: Method called based on ONE type (receiver)
  • Double dispatch: Method called based on TWO types
  • How visitor achieves it: element.accept() → visitor.visit_X()

Example to give:

“Normal method calls use single dispatch - the method called depends on the object’s type. Visitor achieves double dispatch: first accept() is called on the element (first dispatch based on element type), then that calls visitor.visit_X(this) (second dispatch based on visitor type). This lets us choose behavior based on BOTH types.”

Must discuss:

  • Visitor - Multiple operations, multiple element types
  • Strategy - One operation, multiple algorithms
  • Key difference - Visitor separates algorithm from objects; Strategy swaps algorithms

Example to give:

Strategy Pattern is about swapping ONE algorithm - like choosing between QuickSort and MergeSort for sorting. Visitor Pattern is about performing MANY operations across MANY types - like exporting documents to HTML, Markdown, and PDF. Each visitor is an operation that handles all element types.”

Benefits to mention:

  • Easy to add operations - One new visitor class
  • Single Responsibility - Each visitor = one operation
  • Related code together - All HTML logic in one place
  • Accumulate state - Count, aggregate as you visit

Trade-offs to acknowledge:

  • Hard to add elements - Add visit method to ALL visitors
  • Breaking encapsulation - Visitors need element internals
  • Complexity - More classes, double dispatch

Q: “When would you NOT use Visitor Pattern?”

A:

“I wouldn’t use Visitor when the element hierarchy changes frequently. Every new element type requires adding a visit method to EVERY visitor - that’s the opposite of Open/Closed Principle. If you’re adding new shapes regularly but operations are stable, use polymorphism instead (each shape implements its own operations).”

Q: “How does Visitor Pattern handle the expression problem?”

A:

“The expression problem is the difficulty of adding both new types AND new operations in a type-safe way. Visitor Pattern solves half of it - it makes adding new operations easy (new visitor class), but makes adding new types hard (modify all visitors). For systems where operations change more than types, Visitor is perfect.”

Q: “Where is Visitor Pattern commonly used?”

A:

“Visitor is common in compilers and interpreters - the AST structure is stable but you need many operations: type checking, code generation, optimization, pretty printing. Document processors use it too - document elements are stable but you need HTML export, PDF export, word count, spell check, etc.”

Before your interview, make sure you can:

  • Define Visitor Pattern clearly
  • Explain double dispatch
  • Compare Visitor vs Strategy
  • Implement Visitor from scratch
  • Explain the trade-off (operations vs types)
  • List benefits and drawbacks
  • Give real-world examples (compilers, document exporters)
  • Discuss when NOT to use it
  • Draw the class structure

Remember: Visitor Pattern is about adding new operations without modifying classes - separate algorithms from the objects they operate on! 🎯