Visitor Design Pattern Explained

Visitor lets you add new operations to a group of related classes without modifying those classes themselves, by moving the operation into a separate visitor...

Published November 13, 2026

The Visitor pattern lets you add new operations to a set of classes without modifying them, by moving the operation into a separate visitor object that each element 'accepts'.

The concept

Each element has accept(visitor) that calls visitor.visitX(this). The visitor contains one method per element type.

It suits stable class hierarchies with many changing operations, such as ASTs in compilers.

Double dispatch

  1. Client calls node.accept(visitor)
  2. Node calls visitor.visitNode(node)
  3. Visitor runs logic for that type
  4. New operation = new visitor, no node changes

Practical examples

AST nodes

class Num { constructor(v) { this.v = v } accept(vis) { return vis.num(this) } }
class Add { constructor(l, r) { this.l = l; this.r = r } accept(vis) { return vis.add(this) } }

Evaluator visitor

const evalV = { num: (n) => n.v, add: (n) => n.l.accept(evalV) + n.r.accept(evalV) }
new Add(new Num(2), new Num(3)).accept(evalV) // 5

Printer visitor

const show = { num: (n) => `${n.v}`, add: (n) => `(${n.l.accept(show)} + ${n.r.accept(show)})` }

Common mistakes

  • Using it when the element classes change often.
  • Breaking encapsulation by exposing internals.
  • Forgetting every visitor needs a method per type.

Key takeaways

  • Add operations without touching classes.
  • Double dispatch via accept().
  • Best for stable hierarchies.

FAQ

Visitor vs Strategy?

Visitor varies by element type; Strategy varies one algorithm.

Where used?

Compilers, linters, serialisers.

More General articles