Build an Expense Policy Rule Engine
Company: Rippling
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
Quick Answer: This question evaluates implementing rule-evaluation logic, data modeling for expense records and rule representations, operator semantics, and designing for extensibility to support grouped rules and logical composition.
Constraints
- 0 <= len(rules) <= 100000
- Each rule has a valid field and operator
- All comparisons are type-compatible
- For operator 'in', rule['value'] is a collection that supports membership testing
Examples
Input: ([{'id': 'r1', 'field': 'amount', 'operator': '>=', 'value': 100}, {'id': 'r2', 'field': 'category', 'operator': '==', 'value': 'travel'}, {'id': 'r3', 'field': 'country', 'operator': 'in', 'value': ['US', 'CA']}, {'id': 'r4', 'field': 'merchant', 'operator': '!=', 'value': 'Uber'}], {'amount': 120, 'category': 'travel', 'merchant': 'Hilton', 'country': 'US'})
Expected Output: ['r1', 'r2', 'r3', 'r4']
Explanation: All four rules match the expense, so all ids are returned in the original order.
Input: ([], {'amount': 50, 'category': 'meal', 'merchant': 'Cafe', 'country': 'FR'})
Expected Output: []
Explanation: With no registered rules, nothing can match.
Input: ([{'id': 'a', 'field': 'amount', 'operator': '<', 'value': 0}, {'id': 'b', 'field': 'amount', 'operator': '<=', 'value': -20}, {'id': 'c', 'field': 'country', 'operator': 'in', 'value': ['DE', 'FR']}, {'id': 'd', 'field': 'merchant', 'operator': '==', 'value': 'RefundCo'}], {'amount': -20, 'category': 'refund', 'merchant': 'RefundCo', 'country': 'DE'})
Expected Output: ['a', 'b', 'c', 'd']
Explanation: This checks negative amounts, a boundary comparison, membership, and string equality. Every rule matches.
Input: ([{'id': 'r1', 'field': 'amount', 'operator': '>', 'value': 500}, {'id': 'r2', 'field': 'category', 'operator': '==', 'value': 'lodging'}, {'id': 'r3', 'field': 'country', 'operator': '!=', 'value': 'JP'}], {'amount': 300, 'category': 'meal', 'merchant': 'Sushi Bar', 'country': 'JP'})
Expected Output: []
Explanation: The amount is not greater than 500, the category is not lodging, and the country is equal to JP, so no rules match.
Input: ([{'id': 'x1', 'field': 'category', 'operator': 'in', 'value': ['meal', 'travel']}, {'id': 'x2', 'field': 'amount', 'operator': '<', 'value': 50}, {'id': 'x3', 'field': 'country', 'operator': '==', 'value': 'US'}, {'id': 'x4', 'field': 'merchant', 'operator': '!=', 'value': 'Starbucks'}], {'amount': 49.99, 'category': 'meal', 'merchant': 'Starbucks', 'country': 'US'})
Expected Output: ['x1', 'x2', 'x3']
Explanation: The first three rules match. The last rule fails because the merchant is exactly Starbucks.
Hints
- Write a small helper or mapping from operator strings to the corresponding comparison logic.
- Scan the rules once, and append a rule's id whenever its condition is true.