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.
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.
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.
Where it shows up in interviews
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
Where it is used in real software
javac, Babel, and ESLint traverse ASTs with visitors for type checking, transforms, and linting.
Files.walkFileTree calls a visitor for directories and files.
Generates visitor interfaces for parse trees.
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.
How it works, step by step
- 1Confirm the element types are stable
Adding types later is costly.
- 2Define the visitor interface
visitCircle, visitRectangle...
- 3Add accept() to each element
visitor.visitCircle(this).
- 4Implement one visitor per operation
AreaVisitor, SvgExportVisitor.
- 5Traverse composites
Composite accept() visits its children.
Adding operations vs adding types
3 shapes (Circle, Rect, Triangle) and 3 operations
| Change | Polymorphism (methods on shapes) | Visitor |
|---|---|---|
| Add operation 'export SVG' | Edit all 3 shape classes | Add 1 visitor class |
| Add shape 'Hexagon' | Add 1 class | Edit 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.
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();Complexity and performance
Per element.
New type: edit all visitors.
Trade-offs
Easy to add operations, hard to add element types.
Visitors often need access to element internals, which may force public getters.
Variants and related techniques
Java 21 switch on sealed types and TypeScript discriminated unions give exhaustive checks without accept().
Walk trees such as ASTs or file systems.
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.
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.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Shopping cart tax and shipping visitors | Medium | Double dispatch. |
| Expression tree evaluator and printer | Medium | AST visitors. |