Evaluation semantics
How Gates decides. Every answer comes with a reason telling you which step decided — use it for debugging, never for branching in product code.
Feature flags
Checked in order — first hit wins:
- Environment switch — flag off in this environment? Serve off.
- Overrides — a user id or email domain pinned to on/off in the dashboard. First match wins.
- Conditions — if the flag has conditions and all of them match, serve on. If they don't match, move on to the rollout (they don't serve off by themselves).
- Rollout — the percentage decides. 100% is on for everyone, 0% off for everyone, anything between buckets the user by
userId.
reason | Meaning |
|---|---|
flag_off | Off in this environment. |
override | A user-id or domain override matched. |
conditions | All conditions matched — served on. |
rollout | The percentage decided. |
missing_user_id | Partial rollout but no userId to bucket by. |
unsupported_rule | The flag uses a rule this SDK version doesn't know — served off. Upgrade the SDK. |
Experiments
Checked in order:
- Not running →
not_running - Off in this environment →
environment_off - Audience doesn't match →
not_in_audience(audience deleted →audience_not_found) - No
userId→missing_user_id - In the holdout →
holdout - Outside the traffic percentage →
not_in_traffic - Otherwise →
assigned, with a variant{ id, name, isControl }
Every assignment also carries an iteration number. Restarting an experiment bumps it and reshuffles everyone — record it with your events so results never mix populations.
Conditions
Traits are compared as strings, the same way on every platform.
| Operator | Matches when |
|---|---|
is | Trait equals the value exactly. |
is_not | Trait differs (or is unset). |
contains | Trait contains the value, ignoring case. |
gt / lt | Greater/less than. Numeric if both sides are numbers, otherwise text order. |
is_set | Trait is present and non-empty. |
Stickiness
Rollouts and assignments aren't random — they're a hash of the user id and a salt stored with the gate:
- The same user gets the same answer every time, on every platform.
- Renaming a gate never reshuffles who's in.
- Restarting an experiment uses a new salt, so everyone reshuffles — that's the point of a restart.
Fail-safety
When the SDK meets something it doesn't understand, it answers safely instead of guessing:
- A rule added after your SDK version shipped → the whole gate answers
unsupported_rule(flag off, experiment unassigned). - An experiment's audience was deleted →
audience_not_found, nobody is assigned.