You’re building a rules engine. Business users need to define conditions: “if customer.age > 18 AND customer.country IN [‘US’, ‘UK’]”. You need to parse these rules and evaluate them against data.

Interpreter builds a language for these expressions.

What is the Interpreter Pattern?

Interpreter defines a grammar as classes. Each rule becomes a class with an interpret method. Complex expressions are trees of these classes, evaluated recursively.

Interpreter Design Pattern class diagram showing Expression interface with Terminal and Compound implementations that recursively evaluate grammar rules

Terminal nodes are leaves (like numbers or variables). Compound nodes combine other expressions (like AND, OR, or arithmetic operators).

When to Use Interpreter

Use Interpreter When Skip Interpreter When
Grammar is simple and stable Grammar is complex
Performance isn’t critical You need high performance
You’re building a DSL General-purpose parsing needed
Expressions are evaluated often One-time parsing

For complex languages, use parser generators like ANTLR or parser combinator libraries.

Implementation

Boolean Expression Evaluator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Expression interface
public interface BooleanExpression {
    boolean interpret(Context context);
}

// Context holds variable values
public class Context {
    private final Map<String, Object> variables = new HashMap<>();
    
    public void setVariable(String name, Object value) {
        variables.put(name, value);
    }
    
    public Object getVariable(String name) {
        return variables.get(name);
    }
    
    public int getInt(String name) {
        return (Integer) variables.get(name);
    }
    
    public String getString(String name) {
        return (String) variables.get(name);
    }
}

// Terminal: Variable reference
public class Variable implements BooleanExpression {
    private final String name;
    
    public Variable(String name) {
        this.name = name;
    }
    
    @Override
    public boolean interpret(Context context) {
        Object value = context.getVariable(name);
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        throw new IllegalArgumentException("Variable " + name + " is not boolean");
    }
}

// Terminal: Constant
public class Constant implements BooleanExpression {
    private final boolean value;
    
    public Constant(boolean value) {
        this.value = value;
    }
    
    @Override
    public boolean interpret(Context context) {
        return value;
    }
}

// Non-terminal: AND
public class AndExpression implements BooleanExpression {
    private final BooleanExpression left;
    private final BooleanExpression right;
    
    public AndExpression(BooleanExpression left, BooleanExpression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public boolean interpret(Context context) {
        return left.interpret(context) && right.interpret(context);
    }
}

// Non-terminal: OR
public class OrExpression implements BooleanExpression {
    private final BooleanExpression left;
    private final BooleanExpression right;
    
    public OrExpression(BooleanExpression left, BooleanExpression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public boolean interpret(Context context) {
        return left.interpret(context) || right.interpret(context);
    }
}

// Non-terminal: NOT
public class NotExpression implements BooleanExpression {
    private final BooleanExpression expression;
    
    public NotExpression(BooleanExpression expression) {
        this.expression = expression;
    }
    
    @Override
    public boolean interpret(Context context) {
        return !expression.interpret(context);
    }
}

// Comparison expressions
public class GreaterThan implements BooleanExpression {
    private final String variableName;
    private final int value;
    
    public GreaterThan(String variableName, int value) {
        this.variableName = variableName;
        this.value = value;
    }
    
    @Override
    public boolean interpret(Context context) {
        return context.getInt(variableName) > value;
    }
}

public class Equals implements BooleanExpression {
    private final String variableName;
    private final Object value;
    
    public Equals(String variableName, Object value) {
        this.variableName = variableName;
        this.value = value;
    }
    
    @Override
    public boolean interpret(Context context) {
        return value.equals(context.getVariable(variableName));
    }
}

public class InList implements BooleanExpression {
    private final String variableName;
    private final Set<Object> values;
    
    public InList(String variableName, Object... values) {
        this.variableName = variableName;
        this.values = new HashSet<>(Arrays.asList(values));
    }
    
    @Override
    public boolean interpret(Context context) {
        return values.contains(context.getVariable(variableName));
    }
}

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Build expression: age > 18 AND country IN ['US', 'UK']
BooleanExpression ageCheck = new GreaterThan("age", 18);
BooleanExpression countryCheck = new InList("country", "US", "UK");
BooleanExpression rule = new AndExpression(ageCheck, countryCheck);

// Evaluate against different contexts
Context customer1 = new Context();
customer1.setVariable("age", 25);
customer1.setVariable("country", "US");
System.out.println(rule.interpret(customer1));  // true

Context customer2 = new Context();
customer2.setVariable("age", 16);
customer2.setVariable("country", "US");
System.out.println(rule.interpret(customer2));  // false (age)

Context customer3 = new Context();
customer3.setVariable("age", 30);
customer3.setVariable("country", "FR");
System.out.println(rule.interpret(customer3));  // false (country)

// Complex rule: (age > 18 AND country IN ['US', 'UK']) OR isPremium
BooleanExpression isPremium = new Variable("isPremium");
BooleanExpression complexRule = new OrExpression(rule, isPremium);

Context customer4 = new Context();
customer4.setVariable("age", 16);
customer4.setVariable("country", "FR");
customer4.setVariable("isPremium", true);
System.out.println(complexRule.interpret(customer4));  // true (premium overrides)

Mathematical Expression Evaluator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public interface MathExpression {
    double interpret(Map<String, Double> variables);
}

public class Number implements MathExpression {
    private final double value;
    
    public Number(double value) {
        this.value = value;
    }
    
    @Override
    public double interpret(Map<String, Double> variables) {
        return value;
    }
}

public class VariableRef implements MathExpression {
    private final String name;
    
    public VariableRef(String name) {
        this.name = name;
    }
    
    @Override
    public double interpret(Map<String, Double> variables) {
        return variables.getOrDefault(name, 0.0);
    }
}

public class Add implements MathExpression {
    private final MathExpression left;
    private final MathExpression right;
    
    public Add(MathExpression left, MathExpression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public double interpret(Map<String, Double> variables) {
        return left.interpret(variables) + right.interpret(variables);
    }
}

public class Multiply implements MathExpression {
    private final MathExpression left;
    private final MathExpression right;
    
    public Multiply(MathExpression left, MathExpression right) {
        this.left = left;
        this.right = right;
    }
    
    @Override
    public double interpret(Map<String, Double> variables) {
        return left.interpret(variables) * right.interpret(variables);
    }
}

// Usage: (price * quantity) + tax
MathExpression price = new VariableRef("price");
MathExpression quantity = new VariableRef("quantity");
MathExpression tax = new VariableRef("tax");

MathExpression subtotal = new Multiply(price, quantity);
MathExpression total = new Add(subtotal, tax);

Map<String, Double> order = Map.of(
    "price", 29.99,
    "quantity", 3.0,
    "tax", 8.50
);

System.out.println("Total: $" + total.interpret(order));  // Total: $98.47

Simple Parser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class ExpressionParser {
    
    // Parse simple expressions like "age > 18 AND country = US"
    public BooleanExpression parse(String input) {
        List<String> tokens = tokenize(input);
        return parseExpression(tokens, 0).expression;
    }
    
    private List<String> tokenize(String input) {
        return Arrays.asList(input.split("\\s+"));
    }
    
    private ParseResult parseExpression(List<String> tokens, int pos) {
        ParseResult left = parseSimple(tokens, pos);
        
        if (left.nextPos < tokens.size()) {
            String op = tokens.get(left.nextPos);
            if (op.equalsIgnoreCase("AND")) {
                ParseResult right = parseExpression(tokens, left.nextPos + 1);
                return new ParseResult(
                    new AndExpression(left.expression, right.expression),
                    right.nextPos
                );
            } else if (op.equalsIgnoreCase("OR")) {
                ParseResult right = parseExpression(tokens, left.nextPos + 1);
                return new ParseResult(
                    new OrExpression(left.expression, right.expression),
                    right.nextPos
                );
            }
        }
        
        return left;
    }
    
    private ParseResult parseSimple(List<String> tokens, int pos) {
        String variable = tokens.get(pos);
        String operator = tokens.get(pos + 1);
        String value = tokens.get(pos + 2);
        
        BooleanExpression expr;
        switch (operator) {
            case ">":
                expr = new GreaterThan(variable, Integer.parseInt(value));
                break;
            case "=":
                expr = new Equals(variable, value);
                break;
            default:
                throw new IllegalArgumentException("Unknown operator: " + operator);
        }
        
        return new ParseResult(expr, pos + 3);
    }
    
    private static class ParseResult {
        final BooleanExpression expression;
        final int nextPos;
        
        ParseResult(BooleanExpression expression, int nextPos) {
            this.expression = expression;
            this.nextPos = nextPos;
        }
    }
}

// Usage
ExpressionParser parser = new ExpressionParser();
BooleanExpression expr = parser.parse("age > 18 AND country = US");

Context ctx = new Context();
ctx.setVariable("age", 25);
ctx.setVariable("country", "US");
System.out.println(expr.interpret(ctx));  // true

How It Works

graph TD
    subgraph AST["Expression Tree: age > 18 AND country IN US,UK"]
        AND[AND]
        GT["> 18"]
        IN["IN 'US','UK'"]
        AGE[age]
        COUNTRY[country]
    end
    
    AND --> GT
    AND --> IN
    GT --> AGE
    IN --> COUNTRY
sequenceDiagram
    participant Client
    participant AndExpr
    participant GTExpr as GreaterThan
    participant InExpr as InList
    participant Context
    
    Client->>AndExpr: interpret(context)
    AndExpr->>GTExpr: interpret(context)
    GTExpr->>Context: getInt("age")
    Context-->>GTExpr: 25
    GTExpr-->>AndExpr: true (25 > 18)
    AndExpr->>InExpr: interpret(context)
    InExpr->>Context: getVariable("country")
    Context-->>InExpr: "US"
    InExpr-->>AndExpr: true ("US" in list)
    AndExpr-->>Client: true (true AND true)

Common Mistakes

1. Complex Grammar

1
2
3
4
5
6
7
8
// Grammar becomes unwieldy with many rules
public class IfThenElse implements Expression { }
public class ForLoop implements Expression { }
public class FunctionCall implements Expression { }
public class Assignment implements Expression { }
// ... 50 more classes

// Better: use a parser generator for complex languages

2. Poor Performance

1
2
3
4
5
6
// Interpreted every time - slow for hot paths
for (int i = 0; i < 1_000_000; i++) {
    expression.interpret(context);  // Parsing overhead each time
}

// Better: compile to bytecode for performance-critical code

3. No Error Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Wrong - unclear errors
@Override
public boolean interpret(Context ctx) {
    return ctx.getInt(name) > value;  // NullPointerException if missing
}

// Better - meaningful errors
@Override
public boolean interpret(Context ctx) {
    Object val = ctx.getVariable(name);
    if (val == null) {
        throw new InterpretException("Variable '" + name + "' not found");
    }
    if (!(val instanceof Integer)) {
        throw new InterpretException("Variable '" + name + "' is not an integer");
    }
    return (Integer) val > value;
}

Real-World Examples

Regular Expressions: Pattern compiles regex into an interpretable structure.

SQL Parsers: Query engines parse SQL into expression trees.

Spring Expression Language (SpEL): #{expression} in Spring configurations.

JEXL/OGNL: Expression languages for Java.

Composite is the structural foundation. Expression trees are composites.

Visitor can traverse expression trees for analysis, optimization, or code generation.

Flyweight can share terminal expressions like common constants.

Wrapping Up

Interpreter turns grammar rules into classes with interpret methods. It’s good for simple DSLs, rule engines, and expression evaluators.

The pattern becomes unwieldy for complex languages. For real parsers, use tools like ANTLR, parser combinators, or compiled approaches.

Use Interpreter when the grammar is simple, the language is stable, and you need to evaluate expressions repeatedly with different contexts.


Further Reading: