Feature Flag Evaluation Engine
Learn this problemProblem statement
Implement a feature flag evaluator over normalized rows derived from flag and user JSON data.
The inputs have these forms:
- Each
flagsrow is[flagKey, enabled, defaultValue], where the last two values are"true"or"false". Flag order is significant. - Each
rulesrow is[flagKey, ruleId, value, rolloutPercentage]. An empty percentage means no rollout gate; otherwise it is an integer from0through100. - Each
conditionsrow 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
usersrow is[userId, attribute1, value1, attribute2, value2, ...]. - Each
queriesrow is[userId, flagKey]. The special key"*"requests all flags for that user.
Condition Operators
eq: the user value equalsoperandexactly.in: the user value equals one token in the comma-separatedoperandlist.regex: the user value fully matches the valid anchored regular expression inoperand.~=: the lowercased ASCIIoperandis 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
truewhen any matched rule has valuetrue; otherwise returnfalse.
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 <= 500 <= rules.length <= 10000 <= conditions.length <= 50001 <= users.length <= 100001 <= 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
1through100. - 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
0through100. - Every
inoperand 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.