I. Interpreter — Behavioral Pattern
Interpreter represents a grammar as a tree of objects, where each object knows how to evaluate itself. A sentence in the grammar — an arithmetic expression, a query, a rule — becomes a tree built from two kinds of nodes: terminal expressions that hold a literal value, and non-terminal expressions that combine other expressions. Every node exposes the same Interpret() method, so evaluating the whole tree just means calling it on the root.
This solves the problem of hard-coding a mini-language's evaluation logic into one large function full of parsing and branching. Without Interpreter, adding a new operation to the grammar usually means editing that one function and re-testing everything else it already handles. Interpreter turns each grammar rule into its own type, so evaluation of a compound expression is just each node asking its children to evaluate themselves and combining the results — the same recursive shape as walking any tree.
It's the same shape as how a calculator app or a spreadsheet formula engine evaluates (A1 + B1) * C1 — the expression is a tree of operations, and evaluating it means recursively evaluating each node.
II. Real-world Example
A coffee shop order total built from a small arithmetic grammar — numbers, additions, and subtractions:
- Expression (interface) — declares
Interpret() float64, the single contract every node in the tree must implement.
- NumberExpression (terminal) — the simplest node. It holds one literal
valueandInterpret()just returns it.
- AddExpression (non-terminal) — holds a
leftandrightExpression.Interpret()recursively interprets both children and returns their sum.
- SubtractExpression (non-terminal) — same shape as
AddExpression, but returns the difference instead.
Because AddExpression and SubtractExpression hold the Expression interface rather than a concrete type, any mix of number, add, and subtract nodes can be nested arbitrarily deep to represent an expression like (5.00 + 2.00) - 1.50.

III. Implementation
expression.go
package interpreter
type Expression interface {
Interpret() float64
}expressions.go
package interpreter
type NumberExpression struct {
value float64
}
func NewNumberExpression(value float64) NumberExpression {
return NumberExpression{value: value}
}
func (e NumberExpression) Interpret() float64 {
return e.value
}
type AddExpression struct {
left Expression
right Expression
}
func NewAddExpression(left Expression, right Expression) AddExpression {
return AddExpression{left: left, right: right}
}
func (e AddExpression) Interpret() float64 {
return e.left.Interpret() + e.right.Interpret()
}
type SubtractExpression struct {
left Expression
right Expression
}
func NewSubtractExpression(left Expression, right Expression) SubtractExpression {
return SubtractExpression{left: left, right: right}
}
func (e SubtractExpression) Interpret() float64 {
return e.left.Interpret() - e.right.Interpret()
}main.go (usage)
orderTotal := interpreter.NewSubtractExpression(
interpreter.NewAddExpression(
interpreter.NewNumberExpression(5.00),
interpreter.NewNumberExpression(2.00),
),
interpreter.NewNumberExpression(1.50),
)
fmt.Printf("Order total: %.2f\n", orderTotal.Interpret())IV. Explain the example above
- The innermost call,
interpreter.NewAddExpression(NewNumberExpression(5.00), NewNumberExpression(2.00)), builds anAddExpressionwhoseleftandrightare bothNumberExpressionleaves.
- That
AddExpressionbecomes theleftchild of the outerSubtractExpression, whoserightchild isNewNumberExpression(1.50). The full tree now represents(5.00 + 2.00) - 1.50.
orderTotal.Interpret()callsSubtractExpression.Interpret(), which callse.left.Interpret()— this recurses into theAddExpression, which itself callsInterpret()on its twoNumberExpressionchildren, returning5.00and2.00, summed to7.00.
SubtractExpression.Interpret()then subtractse.right.Interpret()(1.50) from that7.00, returning5.50.
Running it produces:
*** Example Interpreter ***
Order total: 5.50
*** End of Interpreter ***No part of this code contains an if statement branching on operator type — SubtractExpression always subtracts, AddExpression always adds, and the tree structure itself encodes the order of operations. Building a more complex order total just means nesting more Add/Subtract/Number nodes, never editing any of the three existing types.
V. Conclusion
Interpreter earns its keep when you have a small, stable grammar that needs to be evaluated repeatedly, and expressing that grammar as a tree of objects is clearer than parsing and evaluating a string every time. It keeps each grammar rule isolated in its own type, so the tree can be built, inspected, or partially evaluated without a monolithic parser function.
The trade-off is that Interpreter doesn't scale well to complex or evolving grammars — real language grammars are usually better served by a parser generator or an existing expression library, since a full hand-written tree of types gets unwieldy past a handful of operators. For a simple, fixed set of operations like this order-total example, hard-coding the arithmetic directly is often simpler than the tree of types Interpreter introduces. It pays off once the grammar needs to be built programmatically, reused across multiple evaluations, or extended by adding new node types rather than editing existing logic.
No pattern is universally superior.
This wraps up the Behavioral patterns in this series — all 11 GoF patterns, alongside the 5 Creational and 7 Structural patterns covered earlier, are now implemented in Go with a working example each.
VI. References
- Go Design Patterns (Mario Castro Contreras)




