AcademyDesign Patterns
Interpreter pattern
Evaluating a small tree of grammar rules one node at a time, the class per rule that costs fast past a toy language, and the point a real parser beats one more class.
6 min readUpdated 3 exercises
Interpreter evaluates a small, fixed grammar (numbers, operators, keywords) represented as a tree, one grammar-rule object at a time.
Interpreter is what a Composite looks like once every node also knows how to evaluate itself. You build a tree out of small grammar-rule objects, a number, an addition, a subtraction, and evaluating the whole tree is just asking the root node to interpret() itself, which asks its children to do the same.
The problem it solves
You have a rule you want users to write themselves, such as a filter: price < 50 AND tag = js. Or a formula in a spreadsheet, a search query, a routing rule, a discount condition.
Hardcoding each variant is impossible, because users invent new combinations. Parsing the text with string splitting and if chains gets unmanageable as soon as you add parentheses or OR.
- The rules are data, not code. They come from a user or a database at runtime.
- They nest. A condition contains conditions.
- They need to be evaluated repeatedly against many inputs, so re-parsing the text each time wastes work.
The fix is to represent each piece of the grammar as a small class and turn the text into a tree of those objects once. Evaluating is then a simple recursive call.
Structure
Three parts:
- An expression interface with
interpret(context). - Terminal expressions: the leaves (
NumberExpr), which return a value directly. - Non-terminal expressions: the branches (
AddExpr,SubtractExpr), which combine the results of their children.
class NumberExpr {
constructor(value) { this.value = value; }
interpret() { return this.value; }
}
class AddExpr {
constructor(left, right) { this.left = left; this.right = right; }
interpret() { return this.left.interpret() + this.right.interpret(); }
}
class SubtractExpr {
constructor(left, right) { this.left = left; this.right = right; }
interpret() { return this.left.interpret() - this.right.interpret(); }
}
// (5 + 3) - 2, built as a tree:
// new SubtractExpr(new AddExpr(new NumberExpr(5), new NumberExpr(3)), new NumberExpr(2)).interpret() → 6It is a Composite where every node can evaluate itself. Evaluating the tree needs no special case for depth: interpret() on the root asks each child to interpret itself, and so on down.
Where it is used in real systems
| Use case | The grammar |
|---|---|
| Filters and search queries | price < 50 AND tag = js turned into a condition tree. |
| Formulas | Spreadsheet cells and calculators. |
| Rule engines | Discounts, eligibility and routing rules written by non-developers. |
| Template engines | Handlebars or Liquid templates evaluated against data. |
| Regular expressions | A pattern compiled into a matching tree or automaton. |
| SQL and query languages | Parsed into a plan and executed. |
| Configuration languages | Expressions inside YAML, GitHub Actions or feature flag rules. |
Notice what these have in common: a small, well-defined language, used a lot, defined by someone other than you.
Interpreters you already use
- Regular expressions.
/a(b|c)+/is parsed into a tree the engine walks. - JSON path and GraphQL. A query string is parsed into a tree, then evaluated against data.
- Template literals and Handlebars. Text with placeholders becomes something evaluated per render.
- CSS selectors.
div > .item:hoveris parsed into a tree the browser evaluates against elements. - Feature flag rules. Tools like LaunchDarkly evaluate targeting rules written as small expression trees.
What the pattern does not include
The classic pattern covers only the evaluation side: the classes for each rule. Turning text into the tree, the parser, is a separate job, and in practice it is the bigger part.
- For a tiny grammar, a hand-written recursive parser is enough.
- For anything bigger, use a parser generator or library (Peggy, Ohm, ANTLR) rather than writing one by hand.
- Often the best choice is to avoid writing a language at all and accept structured JSON (
{ "and": [...] }) that maps straight onto the tree.
When not to use it
- The grammar is large or changes often. One class per rule scales badly. Use a parser generator and a proper evaluator.
- Performance matters. Walking a tree of objects is slower than compiled code. Heavy workloads compile expressions to functions instead.
- A structured config is enough. If users pick from a form, you do not need a language.
- Security. Never evaluate user text with
evalornew Function. Interpreter is the safe alternative, because it only performs the operations the grammar defines. It has to be restricted to those operations, or it becomes the same hole. - You need a general language. This pattern is for small languages, not for something users will write programs in.
What interviewers ask
- "What is the Interpreter pattern?" Defining a grammar as classes and evaluating sentences by walking a tree of them.
- "How does it relate to Composite?" It is a Composite where each node evaluates itself.
- "Where would you use it?" A rule or filter language, a calculator, a template engine.
- "Why not just use `eval`?" It runs arbitrary code. An interpreter only does what the grammar allows.
- "What is the downside?" It does not scale to big grammars, and the parser is a separate problem.
The mistake to avoid
An expression class that reaches outside the tree, such as reading global state or calling a network from inside interpret(). Then the same tree gives different answers on different runs, and the grammar is no longer a pure description. Pass anything an expression needs in as a context argument, and keep interpret() a function of its children and that context.
The mechanism above, a tree whose nodes evaluate themselves recursively, is the same in JavaScript, Python or Java. Only the syntax changes.
Now practice it
1 challenge · 2 quizzesReading this page is the cheap half. These are the exercises that make you use it.
- InterpreterProBuild a small expression tree where every node interprets itself and prove it evaluates bottom-up — the Composite-shaped grammar this page walks through.ChallengeSolve · HardChallengeSolve · Hard
- Design Patterns · Behavioral PatternsTen quick questions across Strategy, State, Observer, Command and the rest of the behavioral family, to check the mental model before this one.QuizTake · HardQuizTake · Hard
- Design Patterns · Behavioral Patterns — Series 2ProA second, tougher pass on the same behavioral family, for once the first set feels easy.QuizTake · HardQuizTake · Hard
Read next
Visitor pattern
Adding a new operation to a fixed set of classes without editing them, the every-visitor update a new node type costs, and why it fails once classes change more than the ops.
6 min
Iterator pattern
Walking a collection without exposing how it is stored, the extra object between caller and data it costs, and the wrong answer when next() runs past the end or data mutates.
5 min