Evaluation order

The exact precedence the SDKs follow to resolve a flag.

When you call enabled() or getValue(), the SDK resolves the flag against the cached config in a fixed order. Every SDK follows these exact steps — this precedence is part of the cross-language parity contract.

Throughout, enabled value means true for a boolean flag, or the flag's enabled_value (falling back to default_value if unset) for a non-boolean (string / number / json) flag. Default value is the flag's default_value.

The five steps

  1. Flag disabled → default value. If the flag is off, return the default value immediately. Nothing else is considered.

  2. No user context → rollout-or-default. If you didn't pass a user, there's no one to match rules against or to bucket. Return the enabled value only if the rollout is 100 (serve-everyone); otherwise the default value.

  3. Rule groups → enabled value. With a user present, check the targeting groups in order. The first group whose conditions all match (AND within a group; OR across groups) returns the enabled value. A matched user gets the feature regardless of the rollout percentage.

  4. Rollout percentage. If no rule matched and the user has a user_id (or id), bucket them: sha256("{user_id}:{flag_key}") % 100. Return the enabled value if the bucket is less than rollout_pct, otherwise the default. (At rollout_pct = 100, every bucket 0–99 is below 100, so everyone is included.)

  5. Otherwise → default value. If nothing matched — no rule, and no usable user_id to bucket (so only a 100% rollout would have applied) — return the default value.

In short

  • Off beats everything → default.
  • A matching rule beats the rollout → enabled.
  • The rollout decides the rest → enabled if in-bucket, else default.
  • No way to decide (no user / no id, rollout below 100) → default.

The default value is the safe fallback at every dead end, which is why it should always be the conservative choice (usually your existing behaviour).

Why no-context and no-id behave the same

Steps 2 and 5 both fall back to "enabled only if rollout is 100." A rollout needs an id to bucket; without one, the only sensible interpretation of a partial rollout is "not this caller," while a full rollout still means everyone. This keeps a flag at 100% behaving like a simple on/off even when no user is passed.

Next