What is the Interpreter Pattern?
Learn the Interpreter design pattern — grammar rules as classes, terminal vs non-terminal expressions — with a Java example and interview Q&A.
Expected Interview Answer
The Interpreter pattern defines a class-based representation of a language’s grammar, using an object structure where each grammar rule is a class, and an interpret() method walks that structure to evaluate sentences in the language.
Each terminal symbol (a literal value) and non-terminal symbol (a combination of expressions, like AND or PLUS) gets its own Expression class implementing a common interface with an interpret(context) method. A sentence in the language is parsed into a tree of these expression objects, and evaluating it is simply a recursive traversal — interpret() calling interpret() on child nodes until literal values resolve. A shared context object typically carries variable bindings or global state used during interpretation. The pattern works well for small, stable grammars — like simple rule engines, boolean expression evaluators, or configuration DSLs — but grows unwieldy for complex languages, where a parser generator is usually the better choice.
- Makes each grammar rule easy to implement, test, and extend in isolation
- Represents a language as an object structure that can be evaluated recursively
- Well suited to small domain-specific languages and rule engines
- New expression types can be added without touching existing ones
AI Mentor Explanation
A scoring app parses shorthand notation like '4' or 'W' into distinct token objects, each of which knows how to update the scoreboard when interpreted — a RunExpression adds runs, a WicketExpression increments the wicket count. Reading a full over just means interpreting each token in sequence against the shared match context. Nested notation, like a no-ball followed by runs, becomes a composite expression whose interpret() delegates to its sub-tokens, exactly mirroring how the Interpreter pattern builds a tree of expression objects for a small grammar.
Step-by-Step Explanation
Step 1
Define the grammar
Identify terminal symbols (literals) and non-terminal symbols (combinations like AND, PLUS) in the small language.
Step 2
Model each rule as a class
Create an Expression interface with interpret(context), and one class per grammar rule.
Step 3
Build the expression tree
Parse a sentence into a tree of terminal and non-terminal expression objects.
Step 4
Interpret recursively
Call interpret() on the root; non-terminal expressions delegate to their children until literals resolve.
What Interviewer Expects
- A clear definition: grammar rules represented as classes, evaluated via interpret()
- Distinction between terminal and non-terminal expressions
- Awareness that it suits small, stable grammars, not full programming languages
- Knowledge of the shared context object carrying variable state during interpretation
Common Mistakes
- Confusing Interpreter with a general-purpose parser generator or compiler
- Applying the pattern to a large, evolving grammar where it becomes unmaintainable
- Forgetting the context object needed to hold variable bindings during interpretation
- Mixing parsing logic into the expression classes instead of keeping interpret() focused on evaluation
Best Answer (HR Friendly)
“The Interpreter pattern is about representing the rules of a small language as classes, so that evaluating a sentence in that language just means walking through those classes and calling an interpret method on each piece. It works well for small rule engines or simple expression languages, but for anything resembling a full programming language, a dedicated parser is a better fit.”
Code Example
interface Expression {
boolean interpret(java.util.Map<String, Boolean> context);
}
class VariableExpression implements Expression {
private final String name;
VariableExpression(String name) { this.name = name; }
public boolean interpret(java.util.Map<String, Boolean> context) {
return context.getOrDefault(name, false);
}
}
class AndExpression implements Expression {
private final Expression left, right;
AndExpression(Expression left, Expression right) {
this.left = left; this.right = right;
}
public boolean interpret(java.util.Map<String, Boolean> context) {
return left.interpret(context) && right.interpret(context);
}
}
Expression isEligible = new AndExpression(
new VariableExpression("hasIncome"),
new VariableExpression("hasGoodCredit")
);
java.util.Map<String, Boolean> context = java.util.Map.of(
"hasIncome", true, "hasGoodCredit", true
);
boolean result = isEligible.interpret(context); // trueFollow-up Questions
- When would you choose Interpreter over writing a general parser?
- What is the difference between a terminal and non-terminal expression?
- Why does the pattern struggle to scale for large or evolving grammars?
- How does the shared context object function during interpretation?
MCQ Practice
1. What does the Interpreter pattern primarily model?
Interpreter represents grammar rules as classes and evaluates sentences by recursively calling interpret() across the object tree.
2. What best describes a non-terminal expression in this pattern?
Non-terminal expressions combine other expressions (terminal or non-terminal) and delegate interpretation to their children.
3. The Interpreter pattern is best suited for?
For large or evolving grammars, a parser generator scales better; Interpreter shines for small, stable domain-specific languages.
Flash Cards
Interpreter pattern in one line? — Represent a language’s grammar as classes, and evaluate sentences by recursively calling interpret().
Terminal vs non-terminal expression? — Terminal is a literal value; non-terminal combines other expressions, like AND or PLUS.
What carries variable state during evaluation? — A shared context object passed into interpret() calls.
When does it stop being a good fit? — When the grammar grows large or complex — a parser generator becomes the better tool.