SwadhyAI Notes

All Low-Level Design notes

Behavioral · Gang of Four

Interpreter

Interpreter is a behavioral pattern that turns a small language into an object tree you can evaluate. Each expression node knows how to interpret itself against a context.

Example: a product manager writes status = PAID AND amount > 500. You can bury that in nested ifs inside search(). Next week they add OR and another field. An expression tree grows with the grammar instead of with one giant method.

The sentence as a tree

And sits on top; leaves do the facts

Walk starts at And. And asks both children. Each looks at one field. A never knows Ticket’s internals.

combinator

And

left.matches && right.matches

terminal

StatusIs PAID

terminal

GreaterThan 500

Is this a tiny language, or a pile of ifs?

SmellHonest shapeNot this pattern
User types a filter stringInterpreter: parse to an One giant evaluate() that also tokenizes
Rules are code you composeSpecificationA parser you do not need
Folder / file tree, same contractCompositeCalling the tree a language
Named predicates + AND/OR in a productRule engine (often Interpreter + registry)A full programming language

Java: a filter sentence as objects

Two terminals and And/Or show the idea. Stop there unless they ask you to extend the grammar. Keep parse (string → tree) in a different type from matches (tree → boolean).

java
interface Filter {
  boolean matches(Ticket t);
}

class StatusIs implements Filter {
  private final Status status;
  StatusIs(Status status) { this.status = status; }
  public boolean matches(Ticket t) { return t.status() == status; }
}

class GreaterThan implements Filter {
  private final Money min;
  GreaterThan(Money min) { this.min = min; }
  public boolean matches(Ticket t) { return t.amount().greaterThan(min); }
}

class And implements Filter {
  private final Filter left, right;
  And(Filter left, Filter right) { this.left = left; this.right = right; }
  public boolean matches(Ticket t) { return left.matches(t) && right.matches(t); }
}

class Or implements Filter {
  private final Filter left, right;
  Or(Filter left, Filter right) { this.left = left; this.right = right; }
  public boolean matches(Ticket t) { return left.matches(t) || right.matches(t); }
}

Filter paidBig = new And(new StatusIs(Status.PAID), new GreaterThan(Money.of(500)));

Keep the language tiny

If you need precedence, a real parser, and types, you are past machine-coding. Show a hand-built tree for AND/OR and two predicates. That proves you can turn a sentence into objects instead of a 400-line evaluate().

Visitor is a different pass

Interpreter evaluates a grammar. Visitor is a new walk over a tree you already have — tax, pretty-print, export — without editing File and Folder. See Visitor.

Split build from evaluate

If interpret() is 200 lines of character walking and business rules, you did not model a language. A crude recursive descent that returns Filter objects is enough. Then matches() is boring, which is the win.

When the language is too big

Loops, variables, and user-defined functions are not an LLD Interpreter demo. Say so. Offer two terminals and And/Or, then ask which extra predicate they want.

Specification versus Interpreter

Use Specification when engineers compose rules in code. Use Interpreter when you actually parse a small language — a search box, a rule string, a query the user typed. The class diagrams look similar; the input does not.

One giant parse method that also evaluates

Split “build the tree” from “ask the tree.” Mixing them is how search() becomes untestable.

A full programming language

Two terminals and And/Or show the idea. Stop there unless they ask you to extend the grammar.

Worked filter: build, then ask

Tokenize status = PAID AND amount > 500 into StatusIs and GreaterThan, wrap with And, then call matches on a Ticket. Tests construct the tree by hand — they do not have to go through the parser. Keeping those two jobs separate is the design.

Parser

String in, Filter out. Recursive descent for AND/OR is enough. Do not put Ticket rules here.

Tree

matches(Ticket) only. Unit tests new And(...) without a string. That is how you prove the objects are the language.

Java: Not is still a combinator

Once And and Or exist, Not is one class. A rule engine registry maps the word PAID to StatusIs. Keep that registry out of Filter.

java
class Not implements Filter {
  private final Filter inner;
  Not(Filter inner) { this.inner = inner; }
  public boolean matches(Ticket t) { return !inner.matches(t); }
}

Filter unpaid = new Not(new StatusIs(Status.PAID));
Filter search = new And(unpaid, new GreaterThan(Money.of(500)));

Search box

User-typed query → tree. That is Interpreter. Hard-coded predicates in Java are Specification.

Precedence

If they ask AND vs OR, parenthesize or use a tiny Pratt/recursive descent. Do not hand-wave a 20-token grammar.

Stop the language

No loops, no variables, no user functions. Offer one extra terminal (date range) if they want depth.

Nearby in LLD