Build a Transaction Rule Engine with AND/OR Chains and Nested Parentheses
Company: Stripe
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: hard
Interview Round: Technical Screen
Build a rule engine that decides whether each payment transaction is accepted or denied.
- **Transactions:** a list of records, each a set of named attributes, for example `{"id": "t1", "amount": 1500, "currency": "USD", "country": "US", "card_type": "prepaid"}`. These attribute names are illustrative.
- **Rules:** a list of rules, each pairing a condition over a transaction's attributes with an action, `ACCEPT` or `DENY`.
- **Output:** one decision per transaction, in input order.
Assume a condition arrives as text, such as `amount > 1000`. A comparison is a field name, an operator (`==`, `!=`, `>`, `>=`, `<`, `<=`) and a literal value, which is a number or a double-quoted string.
The task grows in parts, each extending what a condition can express. Three parts are given below. The round continues with further parts that add more kinds of conditions, and finishing all of them is not expected, so structure the code so that each new part is a small, local change.
An AI coding assistant is available during the round. Use it as a tool you direct: you decide the modules and their interfaces, hand it well-scoped pieces, and check what it produces, rather than letting it produce one monolithic solution.
### Clarifying Questions
- When several rules match one transaction, which one decides: the first matching rule in list order, any matching `DENY`, or something else? What is the decision when no rule matches?
- Do rules arrive as text that must be parsed, or as already-structured objects?
- What should a comparison return when the transaction lacks the field it tests, or when the types do not match, as in `country > 5`?
- Should a malformed rule be rejected when the rules are loaded, or skipped when transactions are evaluated?
- Are amounts integers in the smallest currency unit, or decimals?
### Part 1 — Single-condition rules
Each rule's condition is a single comparison. Parse the rules and return a decision for every transaction. For example, the condition `amount > 1000` matches a transaction whose `amount` is 1500 but not one whose `amount` is exactly 1000.
```hint Separate the jobs
Even with one comparison there are distinct jobs: reading the rule text, representing a condition, evaluating it against a transaction, and turning matches into a decision. Decide which of these will have to change in later parts.
```
#### What This Part Should Cover
- A condition representation that is parsed once and evaluated many times
- Correct comparison semantics for numbers and strings, including the boundary of strict operators
- A decision step kept separate from condition evaluation
- Tests for matching, non-matching and malformed rules
### Part 2 — AND and OR without precedence
A condition may now chain comparisons with `AND` and `OR`. The two operators have no precedence over each other: evaluate the chain strictly from left to right. So `A OR B AND C` means `(A OR B) AND C`, not the usual `A OR (B AND C)`.
For the transaction `{"country": "US", "amount": 50, "card_type": "credit"}`, the condition `country == "US" OR amount > 1000 AND card_type == "prepaid"` is false, because `(true OR false) AND false` is false.
```hint Fold as you go
Think about how an already-evaluated left side combines with the next comparison, and whether splitting the text on spaces or keywords will survive the next part.
```
#### Clarifying Questions for this Part
- Are `AND` and `OR` always uppercase?
- May a string literal contain the word `AND` or `OR`, or spaces?
#### What This Part Should Cover
- Tokenizing that does not break on spacing or on keywords inside string literals
- Left-to-right combination that matches the stated semantics, verified by a test where the usual precedence would give a different answer
- An extension of Part 1's structures rather than a rewrite
### Part 3 — Nested conditions with parentheses
Conditions may now group sub-conditions with parentheses, nested to any depth, for example `(amount > 1000 AND country != "US") OR (card_type == "prepaid" AND amount > 200)`. A parenthesized group is evaluated as a unit before it is combined with its neighbors. For the transaction `{"amount": 300, "country": "US", "card_type": "prepaid"}`, that condition is true, because the second group is true.
```hint Recursion in the input
A parenthesized group contains a whole condition, which may itself contain groups. Consider which structure in your code can mirror that.
```
#### Clarifying Questions for this Part
- Outside parentheses, does left-to-right evaluation still hold, or should `AND` now bind more tightly than `OR`?
- Is there a maximum nesting depth or rule length?
#### What This Part Should Cover
- Parsing of arbitrary nesting, with clear errors for unbalanced or misplaced parentheses
- A tree representation whose evaluation follows the grouping exactly
- Tests for nested groups and for malformed input
- A change confined to parsing, with evaluation and the decision step untouched
### What a Strong Answer Covers
- A modular design (tokenizing, parsing into a tree, evaluating, deciding) chosen by the candidate before the assistant writes code
- The policies the prompt leaves open (how rules combine, missing fields, malformed rules) raised, decided explicitly, and easy to change
- Incremental progress: each part working and tested before the next one starts
- Effective direction of the AI assistant: scoped requests, reviewed output, and bugs caught by tests
- Readable code with clear names and error messages that point at the offending rule and position
### Follow-up Questions
- How would you add `NOT` and an `IN` operator, as in `country IN ("US", "CA")`, and which modules would change?
- How would you report which rule decided each transaction, and why it matched?
- A new rule needs state across transactions, such as the number of transactions on the same card in the last hour. What changes in your design?
- With thousands of rules and a high transaction rate, where does the time go, and how would you reduce it?
Overview: Multi-part coding exercise: build a rule engine that accepts or denies payment transactions by evaluating text conditions over their attributes. The parts add single comparisons, then AND/OR chains evaluated strictly left to right, then nested parentheses. It tests parser design, modular code, and directing an AI coding assistant.
Read the full Stripe Software Engineer interview experience this question came from