Problem · Design

Feature Flag Evaluation Engine

Learn this problem
HardCresta logoCrestaFULLTIMEONSITE INTERVIEW

Problem statement

Implement a feature flag evaluator over normalized rows derived from flag and user JSON data.

The inputs have these forms:

  • Each flags row is [flagKey, enabled, defaultValue], where the last two values are "true" or "false". Flag order is significant.
  • Each rules row is [flagKey, ruleId, value, rolloutPercentage]. An empty percentage means no rollout gate; otherwise it is an integer from 0 through 100.
  • Each conditions row is [flagKey, ruleId, attribute, operator, operand]. All conditions belonging to one rule are ANDed. A rule with no condition rows passes its condition group.
  • Each users row is [userId, attribute1, value1, attribute2, value2, ...].
  • Each queries row is [userId, flagKey]. The special key "*" requests all flags for that user.

Condition Operators

  • eq: the user value equals operand exactly.
  • in: the user value equals one token in the comma-separated operand list.
  • regex: the user value fully matches the valid anchored regular expression in operand.
  • ~=: the lowercased ASCII operand is a substring of the lowercased ASCII user value.

An absent user attribute makes its condition false. Judged user values are always non-null strings.

Percentage Rollout

For a rollout rule, build flagKey:ruleId:userId and compute its unsigned 32-bit FNV-1a hash: start at 2166136261; for each ASCII byte, XOR the hash with the byte, multiply by 16777619, and reduce modulo 2^32. The rule passes its rollout gate exactly when hash mod 100 < rolloutPercentage.

Decision Rules

  • A disabled flag returns its default and ignores every rule.
  • For an enabled flag, a rule matches only when its condition group and optional rollout gate both pass.
  • If no rule matches, return the flag default.
  • If one or more rules match, return true when any matched rule has value true; otherwise return false.

Return one string per query. A single-flag query returns flagKey=true or flagKey=false. An all-flags query returns those pairs in input flag order, joined by |.

Function

evaluateFeatureFlags(flags: String[][], rules: String[][], conditions: String[][], users: String[][], queries: String[][]) → String[]

Examples

Example 1

flags = [["new_nav","true","false"],["beta_banner","false","true"]]rules = [["new_nav","country_rule","true",""],["new_nav","plan_rule","false",""],["new_nav","vip_rule","true","100"]]conditions = [["new_nav","country_rule","country","eq","US"],["new_nav","plan_rule","plan","in","free,trial"],["new_nav","vip_rule","email","regex","^vip.*@example[.]com$"]]users = [["u1","country","US","plan","free","email","regular@example.com"],["u2","country","CA","plan","trial","email","vip42@example.com"],["u3","country","CA","plan","paid","email","guest@example.com"]]queries = [["u1","new_nav"],["u2","*"],["u3","new_nav"]]return = ["new_nav=true","new_nav=true|beta_banner=true","new_nav=false"]

For u1, the country rule returns true while the plan rule returns false, so true-wins yields true. For u2, the plan rule and the 100% VIP rule both match, again yielding true; disabled beta_banner returns its default true. No rule matches u3, so new_nav returns its default false.

Example 2

flags = [["search","true","false"]]rules = [["search","platform_team","true",""]]conditions = [["search","platform_team","team","~=","PLATFORM"]]users = [["u1","team","Core Platform"],["u2","team","Mobile"]]queries = [["u1","*"],["u2","search"]]return = ["search=true","search=false"]

The ~= operator ignores ASCII letter case. Core Platform contains PLATFORM, while Mobile does not.

Example 3

flags = [["gradual","true","false"]]rules = [["gradual","r1","true","30"]]conditions = []users = [["u1"],["u2"]]queries = [["u1","gradual"],["u2","gradual"]]return = ["gradual=false","gradual=true"]

The FNV-1a buckets for gradual:r1:u1 and gradual:r1:u2 are 64 and 21. Only 21 is below the 30% threshold.

Constraints

  • 1 <= flags.length <= 50
  • 0 <= rules.length <= 1000
  • 0 <= conditions.length <= 5000
  • 1 <= users.length <= 10000
  • 1 <= queries.length <= 500
  • Every flag key, rule ID, user ID, attribute name, user value, operand, and pattern is valid non-null ASCII text of length from 1 through 100.
  • Flag keys, rule IDs within a flag, user IDs, and attribute names within a user are unique. Flag keys contain neither *, |, nor =.
  • Each flag row, rule row, condition row, user row, and query row has exactly the shape described above, and every reference names an existing flag, rule, or user.
  • Every boolean field is exactly "true" or "false".
  • Each rollout percentage is empty or a base-10 integer from 0 through 100.
  • Every in operand is a comma-separated list of non-empty tokens, and its compared user value contains no comma.
  • Every regex operand begins with ^, ends with $, and is valid with the same full-match meaning in Python, Java, and C++14 ECMAScript regex; lookarounds and backreferences are excluded.

More Cresta problems

drafts saved locally
public String[] evaluateFeatureFlags(String[][] flags, String[][] rules, String[][] conditions, String[][] users, String[][] queries) {
    // Write your code here.
}
flags[["new_nav","true","false"],["beta_banner","false","true"]]
rules[["new_nav","country_rule","true",""],["new_nav","plan_rule","false",""],["new_nav","vip_rule","true","100"]]
conditions[["new_nav","country_rule","country","eq","US"],["new_nav","plan_rule","plan","in","free,trial"],["new_nav","vip_rule","email","regex","^vip.*@example[.]com$"]]
users[["u1","country","US","plan","free","email","regular@example.com"],["u2","country","CA","plan","trial","email","vip42@example.com"],["u3","country","CA","plan","paid","email","guest@example.com"]]
queries[["u1","new_nav"],["u2","*"],["u3","new_nav"]]
expected["new_nav=true", "new_nav=true|beta_banner=true", "new_nav=false"]
checking account