Interpreter
Think of a simple calculator that understands expressions like 3 + 4 * 2. Under
the hood, it doesn't just scan the string character by character with a pile of special cases —
it builds a small tree out of the pieces ("multiply 4 and 2," "add 3 to that result") and
evaluates the tree. The Interpreter pattern formalizes that idea:
represent each rule of a simple grammar as its own class with an
interpret() method, and build complex expressions by composing simpler
expression objects into a tree.
⚠️ The Problem
Say you're building a basic rules engine that needs to evaluate conditions like
"age > 18 AND hasLicense" against a user object, and this kind of condition
string shows up in several places in your app. The tempting approach is ad hoc string parsing
— splitting on "AND", checking for comparison operators with regex, handling
"OR" as another special case — scattered across whatever code happens to need it.
As soon as the grammar grows even slightly (nested parentheses, a new operator, a new
comparison type), this hand-rolled parsing logic becomes fragile and duplicated.
✅ The Solution
Define a common Expression interface with one method, interpret(context).
Each grammar rule gets its own class implementing that interface — a
NumberExpression just returns its value, an AddExpression holds two
child expressions and returns the sum of interpreting each of them. Complex expressions are
built by composing simpler ones into a tree: AddExpression(NumberExpression(3),
MultiplyExpression(NumberExpression(4), NumberExpression(2))). Interpreting the root of
the tree recursively interprets every node beneath it, evaluating the whole expression in one
consistent way regardless of how deep or complex it is.
Example 1 — an arithmetic expression tree evaluator
class NumberExpression {
constructor(value) {
this.value = value;
}
interpret() {
return this.value;
}
}
class AddExpression {
constructor(left, right) {
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() + this.right.interpret();
}
}
class MultiplyExpression {
constructor(left, right) {
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() * this.right.interpret();
}
}
// Represents: 3 + (4 * 2)
const expression = new AddExpression(
new NumberExpression(3),
new MultiplyExpression(new NumberExpression(4), new NumberExpression(2))
);
console.log(expression.interpret()); // 11
Nothing in this code parses a raw string at runtime — the tree itself already represents the
expression's structure. Adding a new operation (say, SubtractExpression) means
writing one more small class with the same interpret() shape; nothing else in the
tree needs to change.
Example 2 — a secondary context: a simple rule engine
class Expression:
def interpret(self, context):
raise NotImplementedError
class GreaterThanExpression(Expression):
def __init__(self, field, value):
self.field = field
self.value = value
def interpret(self, context):
return context.get(self.field, 0) > self.value
class HasFlagExpression(Expression):
def __init__(self, field):
self.field = field
def interpret(self, context):
return bool(context.get(self.field, False))
class AndExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self, context):
return self.left.interpret(context) and self.right.interpret(context)
class OrExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self, context):
return self.left.interpret(context) or self.right.interpret(context)
# Represents: age > 18 AND hasLicense
rule = AndExpression(
GreaterThanExpression("age", 18),
HasFlagExpression("hasLicense"),
)
applicant = {"age": 21, "hasLicense": True}
print(rule.interpret(applicant)) # True
rejected_applicant = {"age": 16, "hasLicense": True}
print(rule.interpret(rejected_applicant)) # False
The rule itself — age > 18 AND hasLicense — is expressed entirely as a tree of
small objects, each of which knows how to evaluate itself against a context. New condition types
(an InExpression, a NotExpression) slot in the same way, without
touching the classes that already exist.
👍 When To Use It
- You have a small, stable, well-defined grammar that needs to be evaluated repeatedly — simple rule conditions, basic arithmetic, small configuration expressions.
- You want the grammar's rules to be individually extensible — adding a new kind of expression should mean adding a new class, not editing a shared parser function.
- The grammar is genuinely simple — Interpreter trees are a great fit for small, well-bounded languages, not general-purpose ones.
👎 When NOT To Use It
- When the grammar is anything beyond small and simple — expression trees get slow and unwieldy fast as grammar complexity grows, since every rule becomes another class and every evaluation walks the whole tree recursively.
- When you need real parsing features — operator precedence, error recovery, detailed syntax errors — a dedicated parser generator (ANTLR, PEG.js) or parser-combinator library handles this far better than a hand-built expression tree.
- When an existing expression library already covers your use case — most mainstream languages have mature libraries for exactly this (evaluating math expressions, JSON-based rule conditions) that are better tested than a one-off Interpreter implementation.
❓ FAQ
Is this how real programming language parsers and compilers work?
The general shape — build a tree, then evaluate it — is the same idea behind an abstract syntax tree (AST), which every real compiler and interpreter uses. But production language tooling adds a lot Interpreter alone doesn't cover: proper tokenizing/parsing stages, error recovery, and usually a separate, more efficient execution step rather than naive tree-walking. Interpreter-the-pattern is closer to "the smallest possible version of that idea," useful for small embedded grammars, not full languages.
Is Interpreter something people actually hand-build today?
Rarely, beyond small, simple grammars. For anything beyond a toy expression language or a handful of rule conditions, most real-world parsing uses dedicated parser generators (ANTLR, PEG.js) or existing expression/rule-engine libraries rather than a hand-rolled tree of Expression classes — hand-built interpreter trees get slow and hard to maintain as the grammar grows, since every new syntax feature means another class and the tree-walking evaluation doesn't scale well to complex grammars. Interpreter is genuinely useful, but its sweet spot is intentionally small.
How is this different from just using eval() or a scripting engine?
eval() and embedded scripting engines run a full general-purpose language,
with all the power (and risk — arbitrary code execution) that implies. Interpreter is
usually used for something much narrower and safer: a tiny, fixed grammar you fully
control, evaluated by objects you wrote, with no ability to execute arbitrary code beyond
what your Expression classes explicitly support.
🙋 There Are No Dumb Questions
Q: Doesn't building a tree of tiny classes seem like overkill compared to just
writing an if/else chain to check conditions?
A: For a handful of fixed conditions, an if/else chain is often genuinely simpler and that's
fine. Interpreter starts paying off once the grammar has real structure — nested conditions,
operator combinations, reusable sub-expressions — where a tree of composable objects scales
better than a string of boolean checks glued together with && and
||.
✅ Quick Check
In the Interpreter pattern, how is a complex expression represented?
Next up: what if code that loops over a collection shouldn't need to know whether that collection is an array, a linked list, or a tree underneath? That's the Iterator pattern.