Evaluate Ordered Access-Control Rules
StripeSoftware Engineer · Technical ScreenLast updated
Implement an access-control evaluator that parses rule strings with conditions and returns the first matching decision (ACCEPT or BLOCK).
Problem Statement
Access-control systems often evaluate incoming requests against an ordered list of security rules using first-match semantics. You need to implement an engine that parses these rules and determines the final decision for an incoming request.
Each rule is provided as a string in one of two formats:
- Unconditional rule:
" ALWAYS" - Conditional rule:
" WHERE AND AND ... AND "
`` is either "ACCEPT" or "BLOCK".
Each `` is formatted as , separated by single spaces:
- ``: A string representing the attribute key in the request.
: One of `==`, `!=`,,>=.- ``: Either an integer (e.g.,
500,-10) or an unquoted string token (e.g.,US,mobile).
A rule matches if all of its clauses evaluate to True for the given request payload. If an attribute referenced in a clause is missing from the request, that clause evaluates to False. Numeric comparisons (``, >=) evaluate to False if the attribute in the request is not an integer.
Rules must be evaluated sequentially in the order given. The action of the first matching rule is returned. If no rule matches, return default_action.
Interface
def evaluate_access(
rules: list[str],
request: dict[str, int | str],
default_action: str = "BLOCK"
) -> str:
pass
Input & Output Format
rules: A list of rule strings.request: A dictionary mapping attribute names (strings) to values (integers or strings).default_action: A string, either"ACCEPT"or"BLOCK"(default:"BLOCK").- Return:
"ACCEPT"or"BLOCK".
Constraints
0 <= len(rules) <= 1,000- Each rule contains between
0and20conditions joined by" AND ". requestcontains at most100keys.- Keys and string values consist of alphanumeric characters and underscores.
- Integers fit in standard 64-bit signed integers.
Examples
Example 1
rules = [
"BLOCK WHERE risk_score > 85",
"ACCEPT WHERE country == US AND amount 5000",
"ACCEPT ALWAYS"
]
request = {"country": "US", "amount": 500, "risk_score": 20}
# Output: "ACCEPT"
Explanation: The first rule fails (20 > 85 is False). The second rule matches because country == "US" and `500 85",
"ACCEPT WHERE country == US AND amount 5000"
]
request = {"country": "CA", "amount": 2000, "risk_score": 10}
Output: "BLOCK"
*Explanation:* No rules match. The engine falls back to `default_action`, which is `"BLOCK"`.
## Edge Cases to Consider
- Missing request fields referenced in rule predicates.
- Comparing mismatched types (e.g., `<` operator applied to a string attribute).
- Empty `rules` list returning `default_action`.
- Negative numbers in numeric comparison values.
Hints
Hint 1
Separate the logic into two decoupled steps: parsing a rule string into a structured representation, and evaluating a parsed rule against a request context.
Hint 2
Be careful when parsing values from string rules: distinguish integer literals from string literals so type-specific comparisons behave correctly.
Hint 3
Ensure short-circuit evaluation: within a single rule, stop evaluating clauses on the first False; across rules, stop and return immediately on the first matching rule.
Follow-up questions
- How would you optimize rule evaluation if millions of requests need to be checked against the same fixed set of rules?
- How would you extend the rule syntax to support nested boolean expressions with OR operators and parentheses (e.g., `A AND (B OR C)`)?
- If rules are updated dynamically by multiple concurrent services, how would you ensure thread-safe, zero-downtime rule set reloads?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.