Problem · Design

Rule-Driven Transaction Fraud Detection

Learn this problem
MediumStripe logoStripeFULLTIMEOAPHONE SCREEN
See Stripe hiring insights

Problem statement

Process a finite ordered batch of rule-configuration and transaction-evaluation operations. Maintain active fraud rules by unique rule ID.

Each operation is a string array in one of these forms:

  • ["UPSERT", ruleId, "AMOUNT_AT_LEAST", value]: create or replace a rule that matches when a transaction amount is at least value.
  • ["UPSERT", ruleId, "COUNTRY_IS", value]: create or replace a rule that matches when the country equals value.
  • ["UPSERT", ruleId, "MERCHANT_IS", value]: create or replace a rule that matches when the merchant equals value.
  • ["DELETE", ruleId]: remove that rule. Deleting an unknown ID has no effect.
  • ["EVAL", amount, country, merchant]: evaluate one transaction against the rules active at this point.

An UPSERT with an existing ID replaces the complete old rule before later operations are processed. Country and merchant comparisons are case-sensitive exact string comparisons.

A transaction is fraudulent if at least one active rule matches it. If no active rule matches, it is allowed. Return one boolean for each EVAL operation, in evaluation order: true for fraudulent and false for allowed.

Function

classifyTransactions(operations: String[][]) → boolean[]

Examples

Example 1

operations = [["UPSERT","r1","AMOUNT_AT_LEAST","1000"],["UPSERT","r2","COUNTRY_IS","RU"],["EVAL","900","US","books"],["EVAL","1200","US","books"],["EVAL","20","RU","coffee"]]return = [false,true,true]

The first transaction matches neither rule. The second reaches the amount threshold, and the third matches the country rule.

Example 2

operations = [["UPSERT","risk","AMOUNT_AT_LEAST","100"],["EVAL","100","US","safe"],["UPSERT","risk","MERCHANT_IS","risky"],["EVAL","100","US","safe"],["EVAL","1","US","risky"],["DELETE","risk"],["EVAL","1","US","risky"]]return = [true,false,true,false]

The first evaluation matches the amount rule. Reusing ID risk replaces that rule with a merchant rule. Deleting it leaves no active rules for the final evaluation.

Example 3

operations = [["EVAL","0","US","outlet"],["UPSERT","c","COUNTRY_IS","CA"],["UPSERT","m","MERCHANT_IS","outlet"],["EVAL","0","US","outlet"],["EVAL","0","CA","shop"],["DELETE","missing"],["DELETE","m"],["EVAL","0","US","outlet"]]return = [false,true,true,false]

An evaluation before any rule is allowed. The next two evaluations each match one active rule. Deleting an unknown ID changes nothing; deleting rule m makes the final transaction allowed.

Constraints

  • 1 <= operations.length <= 200000
  • There is at least one EVAL operation.
  • Every operation has one of the valid forms described above.
  • ruleId, country, and merchant are non-empty ASCII strings of length at most 40.
  • Every amount and every AMOUNT_AT_LEAST value is a base-10 integer from 0 through 10^9.
  • At most 200000 rules are active at once.
  • String comparisons are case-sensitive.

More Stripe problems

drafts saved locally
public boolean[] classifyTransactions(String[][] operations) {
    // Write your code here.
}
operations[["UPSERT","r1","AMOUNT_AT_LEAST","1000"],["UPSERT","r2","COUNTRY_IS","RU"],["EVAL","900","US","books"],["EVAL","1200","US","books"],["EVAL","20","RU","coffee"]]
expected[false,true,true]
checking account