This question evaluates a candidate's ability to design a scalable expense policy/rules engine for a corporate card system, testing skills in rule representation, API response modeling, conflict resolution, auditing/explanations, and performance and scalability considerations.
Design a rules engine for a corporate credit‑card expense system where managers define policies. Implement evaluateRules(rules: list<rule>, expenses: list<expense>) -> ?, given expenses as a list of dictionaries with string keys and values. Support at least these rules:
(
1) No restaurant expense over $75;
(
2) No airfare expenses;
(
3) No entertainment expenses;
(
4) No expense over $250. The system must flag violating expenses. What should the function return to serve as an API response? Explain rule representation, extensibility to hundreds of rules, conflict handling and precedence, auditing/explanations, performance/scalability, and how managers select and manage rules. Discuss assumptions and any product-oriented clarifications you would ask before coding.
Quick Answer: This question evaluates a candidate's ability to design a scalable expense policy/rules engine for a corporate card system, testing skills in rule representation, API response modeling, conflict resolution, auditing/explanations, and performance and scalability considerations.
We offer a corporate credit card that employees use for business expenses. Managers set policies on the cards so employees do not misuse them or exceed allowances. You are building the rules engine that powers this product.
An expense is modeled as a dictionary/map with string keys and string values. Policies are rules that govern whether a submitted expense should be flagged for an expense manager to review. We will start with a few simple rules and then add requirements, so design for flexibility.
You are encouraged to ask product-oriented questions and to state your assumptions before writing code.
Sample Expense Data
Expenses are provided as a list of Map<String, String> (string keys and values). The dataset for this problem:
expense_id
trip_id
amount_usd
expense_type
vendor_type
vendor_name
001
001
49.99
supplies
restaurant
Outback Roadhouse
002
001
125.00
supplies
retailer
Staples
003
002
153.00
meals
restaurant
Olive Yurt
004
002
1996.00
airfare
transportation
Southeast Airlines
005
002
34.68
meals
restaurant
The Great Grill
006
002
22.40
meals
restaurant
The Great Grill
007
003
59.50
entertainment
theater
Silver Screen
Constraints & Assumptions
Amounts arrive as strings (e.g.
"153.00"
); the field is
amount_usd
, so assume single-currency USD unless you choose to argue otherwise.
expense_type
and
vendor_type
are
distinct fields
— a "restaurant" rule keys on
vendor_type
, an "airfare" rule keys on
expense_type
. Decide carefully which field each rule targets.
A company may maintain
over a hundred
rules; a manager selects the subset that applies to their team.
evaluateRules
receives the already-selected
rules
list.
In the future, rules will be created via an API, so adding a rule should not require new code or a deploy.
This is a
review/approval
flow (flag for a human), not a real-time card-authorization decline at swipe time.
Part 1 — Per-Expense Rules
Design a system that handles the following starter rules and flags individual expenses that violate them:
You must pass a list of four rules to the rules argument for the policies above. The type of each rule and the return type of evaluateRules are up to you — but discuss and justify the return type before you start coding, because it is consumed as a response by our expenses API and rendered in a UI.
Expected behavior on the sample data: expense 003 is a restaurant meal over $75; expense 004 is both airfare and over $250; expense 007 is entertainment. All three should be flagged for review.
Address the following in your design:
Rule representation
— flexible enough to grow from 4 hard-coded rules to hundreds of manager-selected rules, and later to rule creation via API.
Return type / API response
— what the function returns so downstream services and a UI can answer
which
expenses need review and
why
(which rule(s) each broke).
Conflict handling and precedence
— what happens when one expense matches multiple rules.
Auditing / explanations
— how a manager later sees why an expense was flagged.
Performance and scalability
— cost of evaluation and how it grows.
Manager rule management
— how managers select and manage rules from a large library.
Assumptions and clarifications
— product questions you would ask before coding.
What a Strong Answer Covers Guidance
A
data-driven rule model
(rules as configuration, not per-rule code) that visibly scales to hundreds of rules and to API-created rules.
A justified,
structured return type
that preserves
which
expense,
which
rule(s), and
why
— and serializes cleanly for an API/UI (not a bare boolean or ID list).
Correct handling of
multiple violations per expense
(e.g. 004 = airfare + over $250).
Care distinguishing
expense_type
vs
vendor_type
, and a defensible decision on
boundary values
(is exactly $75 "over $75"?).
A story for
missing / malformed data
(string amounts that don't parse, absent fields) and what the system does with them.
Sensible
complexity
reasoning and a path to scale.
Thoughtful
clarifying questions
and explicitly stated
assumptions
.
Clarifying Questions to Ask Guidance
Is the outcome per expense "approve vs decline at swipe time," or "flag for manager review"? (It changes the whole model.)
If an expense violates several rules, do we report
all
violated rules or just the first?
Which field does each rule target —
expense_type
,
vendor_type
, or
vendor_name
?
Are amounts always single-currency USD, or must we handle multiple currencies?
What should happen when
amount_usd
is missing or unparseable — fail open or flag for review?
Who owns rule storage and selection — does
evaluateRules
get the full library or only the manager's chosen subset?
Part 2 — Aggregate (Per-Trip) Rules
Our return value is used as a response in the expenses API, consumed by a large number of companies, and we keep adding rules. In addition to reviewing individual expenses that violate rules, managers now need to review trips that violate rules.
Add support for rules that cannot be decided from a single expense, such as:
A trip cannot exceed
$2,000
in total expenses.
Total
meal
expenses cannot exceed
$200
per trip.
On the sample data, trip 002 violates both of these rules.
Implement a way to evaluate these more complex, aggregate rules in addition to the per-expense rules from Part 1. The output must surface trips that violate the new rules, alongside the individual flagged expenses from Part 1. As before, discuss the return type before coding — and keep the Part 1 response shape intact so existing API consumers do not break.
What a Strong Answer Covers Guidance
Recognizing that aggregate rules differ from per-expense rules only in
evaluation scope
(group-of-rows vs single row), and exploiting that to
reuse
the Part 1 machinery.
A
backward-compatible
return type that adds trip-level results without breaking the Part 1 expense response.
Correct aggregation on the sample data (trip 002's total and meal total both exceed their caps).
How aggregate rules differ operationally — they are
stateful / need full context
, unlike independent per-expense checks.
Follow-up Questions Guidance
How does evaluation change at
100x scale
(millions of expenses, thousands of rules) — what breaks first, and how do you keep per-expense vs per-trip evaluation fast?
How would you support rule creation
via API
without redeploying, and how do you validate a new rule before it goes live?
How do you make a past flag
reproducible
after the rule set changes (i.e. "which rules were in force when this was flagged")?
A future "auto-approve trusted vendor" exception must
suppress
a flag — how does your conflict/precedence model handle a rule whose effect is to
clear
a violation rather than add one?