skip to content

What is the Visitor design pattern, and what problem does it solve in Java?

level: middleimportance: should knowfreq 55%

answer

  1. Operation moved into its own object
  2. accept(visitor) -> visitor.visit(this)
  3. Easy to add operations, hard to add element types
  4. Open/Closed for operations
  5. AST / compiler traversals

basics

~20 s

Visitor lets you add new operations to a group of related classes without changing those classes. You put each operation in a separate 'visitor' object, and each element class has an accept method that calls the right visit method on the visitor.

solid answer

~40 s

Visitor is a behavioral pattern that separates an algorithm from the object structure it runs on. You have an element hierarchy (e.g. AST nodes, shapes) and a Visitor interface with one visit method per concrete element type. Each element exposes accept(Visitor), which calls back visitor.visit(this). This double indirection lets you add a brand-new operation just by writing a new Visitor implementation, with zero edits to the element classes. It shines when the set of element types is stable but the set of operations grows often (pretty-printers, type-checkers, evaluators over an AST). The cost is the inverse: adding a new element type forces you to touch every existing visitor. So it trades easy operation-extension for hard element-extension.

go deeper

for a junior

Can state the one-liner: Visitor lets you add operations to a class hierarchy without editing those classes, by putting each operation in a separate object with visit methods.

for a middle

Explains the element/visitor split, the accept->visit callback, and names a concrete use case like AST traversal.

for a senior

Articulates the trade-off (easy new operations vs hard new element types), ties it to Open/Closed, and knows when not to use it.

for a principal

Frames it as the expression problem, weighs Visitor against sealed-type pattern matching, and reasons about which axis of change a given design should optimise for.

## The problem Imagine you have a fixed family of related classes — call it an **object structure** or **element hierarchy**. A classic example is the nodes of an abstract syntax tree (AST): `NumberNode`, `AddNode`, `MultiplyNode`. Now you need many *operations* over that tree: evaluate it, pretty-print it, type-check it, compute its depth, optimise it. The naive approach puts each operation as a method on every node class: `evaluate()`, `print()`, `typeCheck()` on each of `NumberNode`, `AddNode`, `MultiplyNode`. Two problems appear: 1. Each node class becomes a grab-bag of unrelated concerns (evaluation logic sits next to printing logic). 2. Every time you add a *new operation*, you must edit *every* node class. That violates the **Open/Closed Principle** (open for extension, closed for modification). ## What Visitor does The **Visitor pattern** moves each operation out into its own object — a *visitor*. Define a `Visitor` interface with one method per concrete element type: ``` interface Visitor { void visit(NumberNode n); void visit(AddNode n); void visit(MultiplyNode n); } ``` An operation like "evaluate" becomes a single class `EvaluateVisitor implements Visitor`. "Pretty-print" becomes `PrintVisitor`. Each gathers all the logic for *that operation across all node types* in one place. To connect a node to a visitor, every element implements an `accept(Visitor v)` method whose only job is to call the visitor back: `v.visit(this)`. This callback is the heart of the pattern (see the double-dispatch question for why it's needed). ## The two roles - **Element** (the data): the node hierarchy. Each concrete element has `accept(Visitor)`. - **Visitor** (the operation): each concrete visitor packages one algorithm over the whole hierarchy. ## Why it's powerful To add a *new operation* — say `DepthVisitor` — you write **one new class** and touch **zero** existing element classes. The element hierarchy stays closed to modification while the system stays open to new operations. That's the win. ## The catch (the Java trade-off) The symmetry cuts the other way: to add a *new element type* (e.g. `SubtractNode`), you must add a `visit(SubtractNode)` method to the `Visitor` interface, which forces you to edit *every* existing visitor. So Visitor is the right tool only when **element types are stable but operations multiply** — the exact opposite of where plain polymorphism (a method on each class) shines. This tension is the famous *expression problem*. ## When to reach for it Compilers/interpreters (AST traversals), document object models, file-system trees, and any "closed set of node types, ever-growing set of analyses" situation. Modern Java offers an alternative — sealed types + pattern-matching `switch` — covered in its own question.

  • When would you NOT use Visitor?
    When the element hierarchy changes frequently (new node types added often) but operations are stable — then a method-per-class polymorphic design is simpler and you avoid editing every visitor on each new element.
  • How does Visitor relate to the Open/Closed Principle?
    It keeps the element classes closed to modification while letting new operations be added as new visitor classes — open for extension along the operation axis (but closed-violating along the element axis).

saying these in an interview costs you the question

  • Saying Visitor makes it easy to add new node/element types (it's the opposite — that's its main weakness)
  • Confusing Visitor with a simple iterator or with the Strategy pattern
  • Claiming Visitor needs no changes to elements at all (you must add accept() to each element once)

context