Skip to content
Sanfor2004
← All articles
Design PatternsDesign PatternsBehavioral PatternsPythonC++Software Engineering

Interpreter (Behavioral Pattern)

By Ahmed (Sanfor) · 11 Sept 2026 · 5 min read
Symbol cards are combined into a small expression using grammar rules.

Start with the design patterns overview · Part 15 of 23 · Behavioral patterns

Represent a small grammar as objects that evaluate expressions in a context. This article follows the example in my 23 Design Patterns repository, connecting the problem, participating classes, Python and C++20 implementations, and the trade-offs that decide whether to use it.

The Problem

Permission rules combine named roles and conjunctions, and rules should be built as data structures.

Naive Solution

bool allowed = roles.contains("editor") && roles.contains("verified");

Why It Becomes a Problem

One hardcoded boolean expression is simple but changing nested rule structures requires changing application code.

The Idea

Role is a terminal expression. Both is a nonterminal that evaluates two child expressions with short-circuit AND.

Reading the illustration

A phrasebook contains rules for combining known words. Each grammar rule knows how to interpret its part of the sentence.

In the repository example, the same design idea addresses this software problem: Permission rules combine named roles and conjunctions, and rules should be built as data structures.

Structure

Diagram · Python source · C++20 source

Interpreter sketch map: Context leads through Both(Expression, Expression) to Role / nested Both.
Interpreter: trace the example from caller through the pattern boundary to its collaborator or result. The arrows show flow, not ownership. Open the full-size diagram.
Context  -->  Both(Expression, Expression)  -->  Role / nested Both

Participants

Expression defines evaluation, Context supplies roles, Role tests membership, Both owns its child expressions.

Canonical roles in this example:

  • Abstract Expression — The contract for evaluating nodes in an Interpreter grammar. Here: Expression.
  • Terminal Expression — An expression with no child expressions. Here: Role.
  • Nonterminal Expression — An expression that combines child expressions according to a grammar rule. Here: Both.
  • Context — The evaluation data used by expressions; here it is the set of role names. Context.

Python Example

The complete Python source is shown first.

class Role:
    def __init__(self, name):
        self.name = name

    def evaluate(self, context):
        return self.name in context


class Both:
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def evaluate(self, context):
        return self.left.evaluate(context) and self.right.evaluate(context)


if __name__ == "__main__":
    rule = Both(Role("editor"), Role("verified"))
    for context in [set(), {"editor"}, {"editor", "verified"}]:
        print(rule.evaluate(context))

Python Output

False
False
True

Code Walkthrough

Expression defines evaluation, Context supplies roles, Role tests membership, Both owns its child expressions.

Start at the final call in the Python example. Follow the middle role in the diagram and compare how the C++20 version handles the same responsibility.

C++20 Example

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <utility>

using Context = std::unordered_set<std::string>;
struct Expression {
    virtual ~Expression() = default;
    virtual bool evaluate(const Context& context) const = 0;
};
class Role final : public Expression {
    std::string name_;
public:
    explicit Role(std::string name) : name_(std::move(name)) {}
    bool evaluate(const Context& context) const override { return context.contains(name_); }
};
class Both final : public Expression {
    std::unique_ptr<Expression> left_, right_;
public:
    Both(std::unique_ptr<Expression> left, std::unique_ptr<Expression> right)
        : left_(std::move(left)), right_(std::move(right)) {
        if (!left_ || !right_) throw std::invalid_argument("Missing expression");
    }
    bool evaluate(const Context& context) const override {
        return left_->evaluate(context) && right_->evaluate(context);
    }
};
int main() {
    const Both rule{std::make_unique<Role>("editor"), std::make_unique<Role>("verified")};
    std::cout << std::boolalpha << rule.evaluate(Context{"editor"}) << '\n';
    std::cout << rule.evaluate(Context{"editor", "verified"}) << '\n';
}

C++20 Output

false
true

When to Use

Use it for a small stable grammar whose expression tree is useful to construct and inspect.

Use cases

Small filtering or eligibility languages fit; this is not a secure authorization system or a general parser.

When NOT to Use

Avoid it for a large language needing robust parsing, diagnostics and optimization; established parser tools are more appropriate.

Advantages

Rules compose recursively and can be evaluated against different contexts.

Trade-offs

Each grammar form adds code. Deep trees risk stack exhaustion, and a parser is deliberately absent: main constructs the syntax tree directly.

Composite · Visitor

Common Confusion

Composite describes the tree structure; Interpreter adds grammar-specific meaning and evaluation. Visitor can add operations over that tree.

Terms to Remember

  • Interpreter — Represent a small language as objects that evaluate its grammar rules.
  • Abstract Expression — The contract for evaluating nodes in an Interpreter grammar. Example: Expression.
  • Terminal Expression — An expression with no child expressions. Example: Role.
  • Nonterminal Expression — An expression that combines child expressions according to a grammar rule. Example: Both.
  • Context — The evaluation data used by expressions; here it is the set of role names.

Interview Vocabulary

  • abstract syntax tree — A tree representing grammatical structure rather than the original text’s surface formatting.
  • recursive composition — Building a structure from parts that expose the same contract as the whole.
  • short-circuit evaluation — Skipping later operands when an earlier result already determines the answer.

Interview Question

Where would precedence be handled if users typed editor AND verified OR admin?

Mini Challenge

Add Either for OR and test a nested rule with three distinct contexts.

Compare the two versions

Both examples build an expression tree directly; neither parses text. Python uses a set as Context and matching evaluate methods. C++ declares an Expression Interface. Use a direct boolean expression when rules do not need to be represented as data. The two examples express the same pattern responsibility; compare their setup and output before changing an input.

Check yourself

  1. Does this example parse text, or evaluate an already built tree?
  2. When would the naive solution on this page be easier to maintain? Give a concrete example.
  3. Change one input in the Python example. Predict the output and explain which responsibility handles the change.

Run and explore the example

The Python code comes from python/main.py, with expected output. The C++20 code comes from behavioral/interpreter/cpp/main.cpp. Follow the repository’s C++20 build instructions to compile it and compare the result with expected.txt. The output demonstrates this example’s behavior; it does not cover every input or the challenge above.

The source example and adapted explanation are © 2026 Sanfor2004, provided under the MIT license. The sketchbook cover is an illustration preserved from this site’s original pattern lessons.

Continue the series

Ahmed (Sanfor)Backend and automation engineer in Alexandria. I build the systems businesses run on, and paint between deploys.

Keep reading

Design PatternsAbstract Factory (Creational Pattern)5 minDesign PatternsAdapter (Structural Pattern)4 minDesign PatternsBridge (Structural Pattern)5 min

Have manual work eating your week?

Book a free call