SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignInterpreter Pattern
Software DesignBehavioral Patterns

Interpreter Pattern

Define a grammar for a language and build an interpreter that processes sentences in that language.

September 8, 20245 min read
design-patternsjavainterpreterbehavioral

One-line Definition

Translates a grammar rule into a class, allowing you to parse and evaluate simple domain-specific languages using an expression tree.


Problem

Suppose your application allows users to filter products using a custom query string like price > 100 AND category = "electronics". Writing a massive string parsing function with dozens of nested if-else statements is fragile, impossible to maintain, and extremely hard to extend. The Interpreter pattern solves this by mapping each rule of the query language (e.g., "AND", "GreaterThan") to its own class, forming an easily traversable expression tree.


Real-world Analogy

Think of Roman numerals. When you read "XIV", you apply rules: X = 10, I = 1, V = 5, and "I before V" means subtract. Each symbol is interpreted according to a simple grammar. An interpreter applies these rules character by character to produce the number 14.


Structure

  • Client
    • Builds the abstract syntax tree (AST) out of terminal and non-terminal expressions, then calls interpret().
  • Context
    • Contains global information or state the interpreter needs during evaluation (e.g., variables, environments).
  • AbstractExpression
    • Interface declaring the interpret() method.
  • TerminalExpression
    • Leaf nodes of the AST. Handles the simplest elements of the grammar (e.g., numbers, variables).
  • NonterminalExpression
    • Branch nodes of the AST. Composes other expressions and recursively evaluates them (e.g., addition, subtraction).

Diagram

classDiagram
    class Client {
    }
    class Context {
    }
    class Expression {
        <<interface>>
        +interpret(Context)
    }
    class TerminalExpression {
        +interpret(Context)
    }
    class NonterminalExpression {
        -Expression left
        -Expression right
        +interpret(Context)
    }
    
    Client --> Expression : calls
    Client --> Context : creates
    TerminalExpression ..|> Expression
    NonterminalExpression ..|> Expression
    NonterminalExpression o-- Expression : composes

Code Walkthrough

Notice how the complex expression (5 + 3) * (10 - 2) is broken down into an Object Tree. When interpret() is called on the root, it recursively evaluates the entire tree.

interface Expression {
    int interpret();
}

class NumberExpression implements Expression {
    private final int number;

    public NumberExpression(int number) {
        this.number = number;
    }

    @Override
    public int interpret() { return number; }
}

class AddExpression implements Expression {
    private final Expression left;
    private final Expression right;

    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() { return left.interpret() + right.interpret(); }
}

class SubtractExpression implements Expression {
    private final Expression left;
    private final Expression right;

    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() { return left.interpret() - right.interpret(); }
}

class MultiplyExpression implements Expression {
    private final Expression left;
    private final Expression right;

    public MultiplyExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() { return left.interpret() * right.interpret(); }
}

class Main {
    public static void main(String[] args) {
        // Tree representing: (5 + 3) * (10 - 2) = 64
        Expression five = new NumberExpression(5);
        Expression three = new NumberExpression(3);
        Expression ten = new NumberExpression(10);
        Expression two = new NumberExpression(2);

        Expression sum = new AddExpression(five, three);       // 5 + 3 = 8
        Expression diff = new SubtractExpression(ten, two);    // 10 - 2 = 8
        Expression result = new MultiplyExpression(sum, diff); // 8 * 8 = 64

        System.out.println("Result: " + result.interpret());
    }
}

Bad vs Good

Bad Approach

Problems

  • Parsing and evaluation logic is mixed together into a giant string-parsing method.
  • Fails catastrophically on nested expressions or when operator precedence matters.
class Calculator {
    public int evaluate(String expression) {
        // Extremely fragile parsing logic
        String[] parts = expression.split(" ");
        int left = Integer.parseInt(parts[0]);
        String operator = parts[1];
        int right = Integer.parseInt(parts[2]);

        if (operator.equals("+")) return left + right;
        if (operator.equals("-")) return left - right;
        throw new IllegalArgumentException("Unknown operator");
    }
}

Better Approach

Improvements

  • Each operator is isolated into its own class.
  • The grammar naturally builds into a tree, inherently solving nesting and precedence.
// Composable expression tree cleanly separates syntax from evaluation
Expression expr = new MultiplyExpression(
    new AddExpression(new NumberExpression(5), new NumberExpression(3)),
    new NumberExpression(2)
);
System.out.println(expr.interpret()); // 16

Pros vs Cons

ProsCons
Maps grammar rules to classes, making languages easy to implementSevere class explosion for complex grammars
Easily extended by adding new Expression classesPoor performance for large or complex languages
Expression trees are composable and highly reusableRequires you to build a parser separately to construct the AST
Excellent for Domain-Specific Languages (DSLs)Complex grammars are much better handled by tools like ANTLR

When to Use

  • You have a simple, recurring language to interpret (e.g., basic math expressions, query filters, rules engines).
  • The grammar is simple and unlikely to change drastically.
  • Efficiency is not the primary concern.

When Not to Use

  • The grammar is complex (e.g., parsing actual programming languages like Java or Python).
  • You need high performance (interpreted trees are slow compared to compiled byte-code).

Real-world Examples

  • java.util.regex.Pattern (Compiles regular expressions into an internal tree structure).
  • java.text.Format and its subclasses (e.g., MessageFormat).
  • Spring Expression Language (SpEL) internally uses AST interpretation.

Key Takeaway

The Interpreter Pattern provides an elegant object-oriented way to evaluate simple Domain Specific Languages by treating every rule as a class. Use it for small rule engines or custom query languages, but immediately switch to a dedicated parser generator (like ANTLR or JavaCC) if the grammar grows beyond a handful of rules.

PreviousAdapter PatternNextBridge Pattern