BEHAVIORAL PATTERNS / OBJECT DESIGN BRIEF

Visitor pattern

The Visitor pattern lets you add new operations to a set of classes without modifying them.

AdvancedPhase 06 / Topic 10 of 10ResponsibilitiesCollaborationsExtensibility
01

Overview

The Visitor pattern lets you add new operations to a set of classes without modifying them. Each element class has an accept(visitor) method that calls visitor.visitX(this); a visitor implements one visit method per element type. New operations (export to JSON, compute tax, render) become new visitor classes.

Visitor is the mirror image of polymorphism: polymorphism makes adding types easy and operations hard; Visitor makes adding operations easy and types hard. It works best when the element hierarchy is stable (AST nodes, document elements, shapes) and operations keep growing. Double dispatch picks the right method based on both the visitor and the element.

A tax inspector visiting businesses

The inspector visits a restaurant, a shop, and a factory, applying different rules to each. Adding a new inspection type (health, fire safety) means a new inspector, not changing every business.

02

When to use it

  • Stable set of element types, many and growing operations.
  • Operations that do not belong in the element classes (export, analysis).
  • Traversing composite structures like ASTs or documents.
03

Where it shows up in interviews

Operations over a stable hierarchy

Recognize it when: many exports or calculations over the same types.

  • Design a document exporter (HTML, PDF, Markdown)
  • Design a shopping cart tax calculator
  • Design a compiler AST
04

Where it is used in real software

Compilers

javac, Babel, and ESLint traverse ASTs with visitors for type checking, transforms, and linting.

Java NIO FileVisitor

Files.walkFileTree calls a visitor for directories and files.

ANTLR

Generates visitor interfaces for parse trees.

05

Key terms

Element
Class with accept(visitor).
Visitor
Interface with visit method per element type.
Double dispatch
Method chosen by both element type and visitor type.
Expression problem
Trade-off between adding types and adding operations.
06

How it works, step by step

  1. 1
    Confirm the element types are stable

    Adding types later is costly.

  2. 2
    Define the visitor interface

    visitCircle, visitRectangle...

  3. 3
    Add accept() to each element

    visitor.visitCircle(this).

  4. 4
    Implement one visitor per operation

    AreaVisitor, SvgExportVisitor.

  5. 5
    Traverse composites

    Composite accept() visits its children.

07

Adding operations vs adding types

3 shapes (Circle, Rect, Triangle) and 3 operations

Step 1 / 2
ChangePolymorphism (methods on shapes)Visitor
Add operation 'export SVG'Edit all 3 shape classesAdd 1 visitor class
Add shape 'Hexagon'Add 1 classEdit every visitor

NOWChange: Add operation 'export SVG' | Polymorphism (methods on shapes): Edit all 3 shape classes | Visitor: Add 1 visitor class

Choose Visitor when operations change more often than types.

08

Implementation

public interface ItemVisitor<R> {    R visitBook(Book b);    R visitElectronics(Electronics e);    R visitFood(Food f);} public interface CartItem { <R> R accept(ItemVisitor<R> v); } public record Book(long priceCents) implements CartItem {    public <R> R accept(ItemVisitor<R> v) { return v.visitBook(this); }}public record Electronics(long priceCents) implements CartItem {    public <R> R accept(ItemVisitor<R> v) { return v.visitElectronics(this); }}public record Food(long priceCents, boolean prepared) implements CartItem {    public <R> R accept(ItemVisitor<R> v) { return v.visitFood(this); }} // New operation without touching item classespublic final class TaxVisitor implements ItemVisitor<Long> {    public Long visitBook(Book b) { return 0L; }    public Long visitElectronics(Electronics e) { return e.priceCents() * 18 / 100; }    public Long visitFood(Food f) { return f.prepared() ? f.priceCents() * 5 / 100 : 0L; }} long tax = List.<CartItem>of(new Book(1200), new Electronics(50000), new Food(800, true))    .stream().mapToLong(i -> i.accept(new TaxVisitor())).sum();
09

Complexity and performance

VisitO(1) double dispatch

Per element.

New operation1 class

New type: edit all visitors.

10

Trade-offs

Operations vs types

Easy to add operations, hard to add element types.

Encapsulation

Visitors often need access to element internals, which may force public getters.

11

Variants and related techniques

Pattern matching

Java 21 switch on sealed types and TypeScript discriminated unions give exhaustive checks without accept().

Visitor + Composite

Walk trees such as ASTs or file systems.

12

Common mistakes

  • Using Visitor with an unstable hierarchy.

    Fix: Every new type breaks all visitors; prefer polymorphism.

  • Heavy boilerplate for two operations.

    Fix: Simple methods on the classes may be clearer.

13

Interview questions

What is double dispatch?

Choosing the method to run based on two runtime types. element.accept(visitor) dispatches on the element, which then calls visitor.visitX(this), dispatching on the visitor.

When should you choose Visitor?

When the set of element types is stable but you frequently add operations over them, such as compiler passes over AST nodes or exporters over document elements.

14

Practice problems

ProblemDifficultyWhat it trains
Shopping cart tax and shipping visitorsMediumDouble dispatch.
Expression tree evaluator and printerMediumAST visitors.