Behavioral 11 — Interpreter
In one line
Interpreter represents a small language (offer rules, search filters, permission policies) as a tree of objects, one class per grammar rule, and evaluates a sentence by asking the tree for its value. In modern Java, sealed interfaces, records and a pattern-matching switch make it short and safe.
Think of it like this
A restaurant's offer card that says "20% off when the bill is over ₹500 AND you order from Pune OR it's your birthday". The card is a tiny language. Anyone who reads it breaks the sentence into parts (conditions joined by AND/OR) and checks each part against the actual order.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Grammar
- The rules for what sentences of a language look like, e.g.
rule := comparison | rule AND rule | rule OR rule | NOT rule. - Terminal expression
- A leaf of the tree that evaluates directly, like
city == "Pune". - Non-terminal expression
- A node built from other expressions, like
And(left, right). - Context
- The data an expression is evaluated against: the current order, user, or request.
- Abstract syntax tree (AST)
- The tree of expression objects that represents one sentence.
Step by step
01The problem: offer rules hard-coded in if-statements
Tiffin's marketing team launches new offers every week: "orders above ₹500 in Pune", "first order OR birthday week", "NOT on Sundays". Each one became a new if block in OfferService, and every change needed a developer and a deploy. What marketing actually needs is a tiny rule language whose sentences can be stored with the offer.
02The fix: one class per grammar rule
Model the grammar as a sealed interface with one record per rule. Evaluation is a single switch that recursively evaluates children, and the compiler checks that every rule type is handled.
import java.util.Map;
sealed interface Rule permits Gt, Eq, And, Or, Not {}
record Gt(String field, long value) implements Rule {} // terminal: total > 50000
record Eq(String field, String value) implements Rule {} // terminal: city == "Pune"
record And(Rule left, Rule right) implements Rule {} // non-terminals combine rules
record Or(Rule left, Rule right) implements Rule {}
record Not(Rule inner) implements Rule {}
final class RuleEngine {
/** The context is the order, flattened to field -> value. */
static boolean eval(Rule r, Map<String, Object> order) {
return switch (r) {
case Gt g -> ((Number) order.get(g.field())).longValue() > g.value();
case Eq e -> e.value().equals(order.get(e.field()));
case And a -> eval(a.left(), order) && eval(a.right(), order);
case Or o -> eval(o.left(), order) || eval(o.right(), order);
case Not n -> !eval(n.inner(), order);
};
}
}03Rules as data
Because a rule is just a tree of records, it can be stored as JSON next to the offer, shown in an admin screen, and reviewed like any other data. A small parser (or a JSON deserializer with type names) turns stored text back into the tree.
Keep the grammar small and the evaluation side-effect free. If rules start needing loops, variables or functions, you are designing a programming language: switch to an existing expression engine (Spring Expression Language, Google's CEL) with sandboxing instead of growing your own.
Try it yourself
- 1
Evaluate an offer rule
"Total over ₹500 AND (city is Pune OR NOT Sunday)" against two orders (amounts in paise).
terminal$ jshell> /open OfferRules.javajshell> Rule offer = new And(new Gt("total", 50000), new Or(new Eq("city", "Pune"), new Not(new Eq("day", "SUN"))))jshell> RuleEngine.eval(offer, Map.of("total", 62000, "city", "Mumbai", "day", "MON"))jshell> RuleEngine.eval(offer, Map.of("total", 62000, "city", "Mumbai", "day", "SUN"))── expected output ──$3 ==> true$4 ==> false
In your stack
- →
Java 21 records, sealed interfaces and pattern-matching
switchturn Interpreter into a few lines, with the compiler checking that every node type is evaluated.java.util.regex.Patterncompiles a regular expression into an internal node tree and interprets it: a built-in example of the pattern.
Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A rule language that grew into a programming language
Marketing asks for "loops over cart items" and "call the loyalty service" inside rules. Developers add ForEach and HttpCall node types to the hand-written interpreter.
Myth vs fact
Myth
Interpreter means writing a full parser and language.
Fact
Most real uses are a handful of node types built from JSON or a builder. Parsing complex text belongs to parser generators and existing engines.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
In production, rule languages need versioning (old orders keep the rule version they were priced with), validation before saving (unknown fields, type mismatches), and limits (maximum depth and node count) so a stored rule can't become a denial-of-service vector.
Remember this
- 1
Interpreter models each grammar rule as a class: terminals (a comparison like
total > 500) and non-terminals (And,Or,Not) that combine other expressions. A sentence becomes a tree of these objects. - 2
Evaluating is a recursive walk: an
Andnode asks both children and combines their answers, a comparison node looks up a value in the context (the current order). - 3
It fits small, stable grammars that business people change often: discount rules, feature-flag targeting, alert conditions, search filters. The rules become data you can store, review and change without redeploying.
- 4
It does not fit big languages. Parsing and evaluating a real query language or scripting language needs a proper parser (ANTLR) or an existing engine (SpEL, CEL, a SQL database), not hand-written classes.
- 5
Interpreter and Composite share the same tree shape; Visitor (Behavioral 10) is often used to add new operations over the tree, such as pretty-printing or validating a rule.
Explain it without notes
What does Interpreter turn a sentence of a small language into, and how is it evaluated?
When should you stop writing your own interpreter?
Practice
Add a Lt(field, value) rule and use it in an offer "total < 30000 OR first order".
Write String show(Rule r) that prints a rule as readable text, e.g. (total > 50000 AND city == Pune).
Trade-offs
- ↔
Business rules become data that can change without deploys, versus a new mini-language to validate, version, secure and document. Keep the grammar tiny or use an existing engine.
Practise it in DSA
The data structure or algorithm inside this design, taught step by step with runnable code in the DSA course:
Done when you can
I can model a small grammar as sealed types and evaluate it recursively.
I can explain when to stop and use an existing expression engine.