🔄 Behavioral

Interpreter Pattern

Defines a representation for a language's grammar along with an interpreter that uses the representation to interpret sentences in the language. Interpreter is used to build domain-specific languages (DSLs) and expression evaluators.

Problem

When a particular type of problem occurs frequently, it can be worthwhile to express instances of the problem as sentences in a simple language. If you hardcode the logic for interpreting each possible expression, the code becomes rigid and impossible to extend without modification. Each new expression type requires changes throughout the system.

Solution

Represent the grammar as a class hierarchy where each rule is a class. Terminal expressions represent literals and variables. Non-terminal expressions combine other expressions using grammar rules. Each expression class implements an interpret method that evaluates the expression in a given context. New rules are added by creating new expression classes.

Participants

RoleResponsibility
AbstractExpressionDeclares an interpret method common to all nodes in the AST
TerminalExpressionImplements interpret for terminal symbols in the grammar
NonterminalExpressionMaintains references to child expressions and implements interpret for grammar rules
ContextContains global information needed during interpretation (variables, environment)

TypeScript Example

TypeScript
interface Expression {
  interpret(context: Record<string, boolean>): boolean;
  toString(): string;
}

class Variable implements Expression {
  constructor(private name: string) {}
  interpret(ctx: Record<string, boolean>) { return ctx[this.name] ?? false; }
  toString() { return this.name; }
}

class And implements Expression {
  constructor(private left: Expression, private right: Expression) {}
  interpret(ctx: Record<string, boolean>) {
    return this.left.interpret(ctx) && this.right.interpret(ctx);
  }
  toString() { return `(${this.left} AND ${this.right})`; }
}

class Or implements Expression {
  constructor(private left: Expression, private right: Expression) {}
  interpret(ctx: Record<string, boolean>) {
    return this.left.interpret(ctx) || this.right.interpret(ctx);
  }
  toString() { return `(${this.left} OR ${this.right})`; }
}

class Not implements Expression {
  constructor(private expr: Expression) {}
  interpret(ctx: Record<string, boolean>) {
    return !this.expr.interpret(ctx);
  }
  toString() { return `NOT ${this.expr}`; }
}

// Rule: (isAdmin OR (isLoggedIn AND NOT isBanned))
const rule = new Or(
  new Variable("isAdmin"),
  new And(
    new Variable("isLoggedIn"),
    new Not(new Variable("isBanned")),
  ),
);

console.log(rule.toString());
console.log(rule.interpret({ isAdmin: false, isLoggedIn: true, isBanned: false })); // true
console.log(rule.interpret({ isAdmin: false, isLoggedIn: true, isBanned: true }));  // false
console.log(rule.interpret({ isAdmin: true, isLoggedIn: false, isBanned: true }));  // true

Real-World Example

Regular expression engines interpret pattern syntax. SQL parsers interpret query strings. CSS selectors are interpreted to match DOM elements. Template engines like Handlebars and EJS interpret template syntax. Rule engines in business applications interpret policy rules. Math expression parsers in calculators and spreadsheets use the Interpreter pattern to evaluate formulas.

When to Use

  • The grammar is simple and efficiency is not a critical concern
  • You need to interpret expressions in a domain-specific language
  • You want to define rules declaratively and evaluate them at runtime
  • The grammar doesn't change frequently but the expressions evaluated change often

When Not to Use

  • The grammar is complex (use parser generators like ANTLR instead)
  • Efficiency is critical—Interpreter creates deep object trees that are slow to traverse
  • The language evolves frequently, requiring constant class hierarchy changes

Understanding the Interpreter Pattern

The Interpreter pattern is one of the most important behavioral design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.

Defines a representation for a language's grammar along with an interpreter that uses the representation to interpret sentences in the language. Interpreter is used to build domain-specific languages (DSLs) and expression evaluators. The pattern involves the following key participants: AbstractExpression, TerminalExpression, NonterminalExpression, Context. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.

In TypeScript and modern JavaScript development, the Interpreter pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Interpreter implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.

Knowing when to apply the Interpreter pattern is as important as knowing how. Common use cases include: The grammar is simple and efficiency is not a critical concern; You need to interpret expressions in a domain-specific language; You want to define rules declaratively and evaluate them at runtime. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.

The Interpreter pattern is related to several other design patterns: Composite, Visitor, Iterator, Flyweight. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.

Related Patterns

More Behavioral Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.