Feature flag logic: combining flags without surprises
Feature flags combine an audience, a master switch and a kill switch, and the surprises come from how they combine. (beta_user OR internal) AND flag_on AND NOT kill_switch is the pattern: four conditions, sixteen rows, and a kill switch that must win every time.
The audience is an OR of groups (beta_user OR internal), the master switch is an AND (flag_on), and the kill switch is an AND NOT. The checks on the page show the rule contingent with the witness internal = TRUE, flag_on = TRUE, the others FALSE, as one row where the feature is enabled. The kill switch is correct because it sits at the top level: no combination of the other three can enable the feature while it is set, which the truth table shows as eight FALSE rows where kill_switch is TRUE.
The surprises are structural. A kill switch placed inside the audience bracket, (beta_user OR (internal AND NOT kill_switch)) AND flag_on, kills the feature for internal users only. Two flags combined with OR when AND was meant turn “both flags must be on” into “either flag suffices”. A flag that defaults to TRUE when unset, in a system where an unset value is not FALSE, enables the feature for everyone during a deploy. Each of these is one row away from the intended rule, and the equivalence checker finds that row.
Flag logic also changes often, which makes it the natural home for the regression method: compare the new rule with the old, review the differing rows against the change request, and keep the counterexamples. The independence pairs give the four tests that show each flag matters, and the rule review checklist has a line for the kill switch's position.
Example
enabled := (beta_user OR internal) AND flag_on AND NOT kill_switch · output
Exact checks
Truth table (16 rows)
| # | beta_user | internal | flag_on | kill_switch | enabled |
|---|---|---|---|---|---|
| 0 | FALSE | FALSE | FALSE | FALSE | FALSE |
| 1 | FALSE | FALSE | FALSE | TRUE | FALSE |
| 2 | FALSE | FALSE | TRUE | FALSE | FALSE |
| 3 | FALSE | FALSE | TRUE | TRUE | FALSE |
| 4 | FALSE | TRUE | FALSE | FALSE | FALSE |
| 5 | FALSE | TRUE | FALSE | TRUE | FALSE |
| 6 | FALSE | TRUE | TRUE | FALSE | TRUE |
| 7 | FALSE | TRUE | TRUE | TRUE | FALSE |
| 8 | TRUE | FALSE | FALSE | FALSE | FALSE |
| 9 | TRUE | FALSE | FALSE | TRUE | FALSE |
| 10 | TRUE | FALSE | TRUE | FALSE | TRUE |
| 11 | TRUE | FALSE | TRUE | TRUE | FALSE |
| 12 | TRUE | TRUE | FALSE | FALSE | FALSE |
| 13 | TRUE | TRUE | FALSE | TRUE | FALSE |
| 14 | TRUE | TRUE | TRUE | FALSE | TRUE |
| 15 | TRUE | TRUE | TRUE | TRUE | FALSE |
Plain-language reading
Beginnerenabled is true exactly when all of the following hold:
1. at least one of beta_user or internal is true (both may be true)
2. flag_on is true
3. kill_switch is false
In every other case, enabled is false.
About the operators:
- AND is true only when all of its parts are true.
- OR is inclusive: it is true when at least one part is true, including when several are.
- NOT reverses a value: true becomes false and false becomes true.
Intermediateenabled is true exactly when all of the following hold:
1. at least one of beta_user or internal is true (both may be true)
2. flag_on is true
3. kill_switch is false
In every other case, enabled is false.
Advancedenabled is true exactly when all of the following hold [conjunction ∧]:
1. at least one of beta_user or internal is true (both may be true) [inclusive disjunction ∨]
2. flag_on is true
3. kill_switch is false [negation ¬]
In every other case, enabled is false.
Formal notation: (beta_user ∨ internal) ∧ flag_on ∧ ¬kill_switch
Logic diagram
The common slip
Putting the kill switch inside a bracket. (beta_user OR (internal AND NOT kill_switch)) AND flag_on still ships the feature to beta users after the switch is thrown. A kill switch is AND NOT kill_switch around the whole rule, and the sixteen-row table shows the eight rows it must zero.
Questions
How do I combine two features that both need to be on?
With AND: feature_a AND feature_b. OR means either suffices. The equivalence checker shows the rows on which the two combinations differ, which are the ones where exactly one flag is on.
What about percentage rollouts?
A percentage bucket is a Boolean condition once decided (in_rollout_bucket), and the rule combines it like any other. The decision of which users fall in the bucket lives outside the rule, in the glossary description.
Related
See also
- Feature flag rule: (beta_user OR internal) AND flag_on AND NOT kill_switch
- Access control rules: roles, exceptions and the banned-user slip
- Regression tests for decision rules that change
- An unassigned input is not FALSE: incomplete assignments
- “A or B and C”: the grouping ambiguity and how to resolve it
- MC/DC independence pairs, worked example with the engine’s coverage report
- A review checklist for decision rules (for analysts and reviewers)
Testing and rules
Generated from the Tangibool engine, semantics tangibool-semantics/1, engine 1.0.0, build . Every table, map, simplification and verdict on these pages was computed by the Tangibool engine when the site was built, and every claim was re-checked by the same exact checker the app uses. Limits are stated where they apply. © 2026 Foreranger LTD.