Katkat

Design a Robust Feature Flag Evaluation Engine

CrestaSoftware Engineer · Onsite

Last updated

Implement an in-memory feature flag evaluation engine that evaluates flags against user attributes using rule predicates, deterministic percentage rollouts, and a true-wins resolution policy.

Problem Statement

Feature flagging allows systems to dynamically toggle functionality for specific users without deploying new code. You are tasked with implementing an in-memory feature flag evaluation engine.

The engine is initialized with a list of flag configurations and evaluates flag states (True or False) for given user profiles.

Configuration and Evaluation Rules

  1. User Schema: A dictionary containing at least an "id" (string), plus arbitrary attribute key-value pairs (e.g., "country": "US", "age": 28).
  2. Flag Schema:
    • key: Unique string identifier for the flag.
    • default_value: Boolean fallback if no rules match.
    • rules: A list of rule definitions evaluated for each user.
  3. Rule Schema:
    • conditions: A list of predicate objects: {"attribute": str, "operator": str, "value": Any}. A rule matches if all its conditions evaluate to true (AND logic). If conditions is empty, it matches unconditionally.
    • rollout_percentage (optional): Integer [0, 100]. If present, the user must fall within this bucket to satisfy the rule.
    • serve: Boolean value (True or False) to return if the rule matches.
  4. Operators:
    • EQUALS: User attribute value equals rule value.
    • IN: User attribute value is present in rule value (list).
    • GTE: User attribute value is greater than or equal to rule value (numerical comparison).
    • Missing/Null handling: If a condition targets an attribute that is missing or None on the user, that condition evaluates to False.
  5. Deterministic Rollout: Use SHA-256 to hash "{flag_key}:{user_id}". Compute bucket as int(sha256_hex, 16) % 100. The user is included if bucket < rollout_percentage.
  6. True-Wins Conflict Resolution: When multiple rules match a user, if any matched rule serves True, the evaluated state is True. If rules match but all serve False, the state is False. If no rules match, fall back to default_value.
  7. Immutability: Input configurations and user objects must never be mutated.

Class Interface

class FeatureFlagEngine:
    def __init__(self, flags_config: list[dict]):
        pass

    def evaluate(self, flag_key: str, user: dict) -> bool:
        """Evaluate a single flag for a user. Return default_value if flag_key is not found."""
        pass

    def evaluate_all(self, user: dict) -> dict[str, bool]:
        """Evaluate all registered flags for a user, returning a map of flag_key -> bool."""
        pass

Example

flags = [
    {
        "key": "beta_dashboard",
        "default_value": False,
        "rules": [
            {
                "conditions": [{"attribute": "country", "operator": "IN", "value": ["US", "CA"]}],
                "rollout_percentage": 50,
                "serve": True
            },
            {
                "conditions": [{"attribute": "beta_tester", "operator": "EQUALS", "value": True}],
                "serve": True
            }
        ]
    }
]

engine = FeatureFlagEngine(flags)
user_a = {"id": "u123", "country": "US", "beta_tester": False}  # sha256("beta_dashboard:u123") % 100 -> e.g. 72 (>= 50)
user_b = {"id": "u456", "country": "UK", "beta_tester": True}

engine.evaluate("beta_dashboard", user_a)  # Returns False (rule 1 rollout misses, rule 2 condition fails)
engine.evaluate("beta_dashboard", user_b)  # Returns True (rule 2 matches)

Constraints

  • Flag keys and user IDs are non-empty ASCII strings.
  • rollout_percentage is an integer between 0 and 100 inclusive.
  • Engine should support O(1) flag lookup and run evaluation efficiently over thousands of users.

Hints

Hint 1

Pre-index the flag definitions in a hash table keyed by flag_key during initialization to allow constant-time lookups.

Hint 2

Isolate rule predicate evaluation into a helper function and safely handle missing or mismatched attribute types without raising exceptions.

Hint 3

For the True-wins policy, iterate through matching rules: if any rule serves True, you can short-circuit and return True immediately.

Follow-up questions

  • How would you support real-time configuration updates to flags without introducing race conditions during concurrent user evaluations?
  • If flag evaluations emit audit telemetry logs, how would you prevent high-throughput evaluation calls from overloading downstream logging pipelines?
  • How would you extend the engine to support multi-variant flags (e.g., string variants 'control', 'variant_a', 'variant_b') instead of only booleans?