← Math in Practice
Concept Logic and classification

Boolean logic, sets, and predicates

Make each condition visible before you decide who passes.

A release engineer opens a canary to European accounts and internal testers. A few accounts are paused for support, including an internal tester. The rollout query selects one of them anyway. Before changing the query, write down what “eligible” means and inspect which clause each account passed.

The judgment to keep

Translate policy into small predicates, combine them deliberately, then check boundary records against the rule. A correct Boolean result only proves that the encoded rule ran; it does not prove that the policy itself is right.

TypeScriptGo Predicates · AND, OR, NOT · sets · truth tables
01 / Read the rollout rule

“European or internal” still has to mean “active and not excluded.”

Suppose the release rule is: send the canary to active accounts in the EU, or active internal testers anywhere, unless support has explicitly excluded that account. The clause “unless excluded” is a hard stop. If it is attached to only one side of an OR, an excluded internal tester can slip through.

We will use five illustrative accounts. acct-17 is active in the EU; acct-23 is an active US internal tester; acct-31 is paused in the EU; acct-42 is an active US internal tester on the exclusion list; acct-58 is an active US account outside the internal group. These are constructed examples, not production records.

Case file / Canary eligibilityExplain every selected account, including the one deliberately rejected.
Allow rule
EU account OR internal tester
Required condition
Account must be active
Safety boundary
Excluded account never receives this rollout
Question
Which inputs satisfy the full rule, and how would we catch a precedence bug?
Keep: the rule's source of authority, the clauses that grant access, and any clause that must veto every grant.
02 / Name the predicates

Give each claim a name before combining it.

For one account, define A = active, E = in the EU, I = an internal tester, and X = explicitly excluded. Each predicate evaluates to true or false for the account. The intended decision is:

eligible = A AND (E OR I) AND NOT X

AND means both required claims hold; OR means at least one branch is sufficient; NOT reverses one Boolean result. Parentheses show that EU and internal are alternative ways to satisfy the allow group, after which activity and exclusion apply to the whole group.

The expression follows standard precedence rules in many languages, but relying on memory makes policy review harder. Parentheses are useful documentation even when the parser would infer the same grouping. Keep predicates named after the evidence they inspect: isActive, isEuAccount, isInternalTester, and isExcluded.

Illustrative values · evaluate the allow group before the final decision
AccountAEIXA ∧ (E ∨ I) ∧ ¬X
acct-17truetruefalsefalsetrue
acct-23truefalsetruefalsetrue
acct-31falsetruefalsefalsefalse
acct-42truefalsetruetruefalse
acct-58truefalsefalsefalsefalse
03 / See the sets

The same rule is a sequence of set operations.

Let U be the accounts in the rollout's candidate population. Let A contain active accounts, E contain EU accounts, I contain internal testers, and X contain exclusions. Then the eligible set is A ∩ (E ∪ I) ∩ (U \ X).

Here, ∩ is intersection (“in both”), ∪ is union (“in either or both”), and U \ X is the complement of exclusions relative to this particular candidate universe. The universe matters: subtracting exclusions from all accounts in a company is not the same as subtracting them from the candidate set being evaluated.

De Morgan's law helps rewrite compound vetoes: NOT (P OR Q) means NOT P AND NOT Q; NOT (P AND Q) means NOT P OR NOT Q. This is useful when translating a policy between a readable statement, a query, and an alert filter. Check that the universe and unknown-value behavior remain the same.

01 / CANDIDATESU

Five accounts under review in this example.

02 / ALLOWE ∪ I

acct-17, acct-23, acct-31, acct-42.

03 / REQUIREDA ∩ (E ∪ I)

Drop paused acct-31; three remain.

04 / VETO\ X

Drop excluded acct-42; select acct-17 and acct-23.

Checkpoint: for each symbol in the expression, point to the field or trusted lookup that supplies it.
04 / Find the logic bug

Reduce the failure to one record and evaluate every clause.

Suppose someone writes (A AND E AND NOT X) OR (A AND I). This looks close, but the exclusion is scoped to the EU branch only. An excluded internal tester can still pass the second branch. The intended rule applies activity, the allow group, and the veto to the account as a whole: A AND (E OR I) AND NOT X. Parentheses also prevent language-specific operator precedence from obscuring that policy during review.

Diagnose by selecting a counterexample, not by staring at a long query. For acct-42, evaluate A=true, E=false, I=true, X=true for acct-42. The flawed expression returns true through its A AND I branch; the intended rule returns false because NOT X is false. This single counterexample exposes the veto-scope bug. Add a non-excluded internal account as a positive boundary case so a fix does not accidentally remove the valid internal rollout path.

For production investigation, capture the account fields as seen by the evaluator, the policy version, and the final decision. Then compare expected and actual predicate values. A policy change can be valid while stale audience membership, cached attributes, or a different query path causes the wrong rollout.

05 / Practice in code

Keep the expression readable at the point of decision.

The examples name the allow predicate, preserve the global activity and exclusion gates, and apply the same account records in TypeScript and Go. Run the code mentally for acct-42: it passes the internal allow branch, then fails the final exclusion gate. Explicit parentheses make the scope reviewable.

Compare the same rollout predicate in TypeScript and Go.

Both versions evaluate the same five records and return the same selected IDs.

TypeScriptCanary eligibility · predicate and set membership
rollout.ts
export type Account = {
	id: string;
	active: boolean;
	region: 'eu' | 'us';
};

/** A candidate receives the canary only when active and selected by at least one allow rule,
 * while never appearing in the explicit exclusion set. The rules are illustrative policy. */
export function canReceiveCanary(
	account: Account,
	internalIds: ReadonlySet<string>,
	excludedIds: ReadonlySet<string>
): boolean {
	const allowed = account.region === 'eu' || internalIds.has(account.id);
	return account.active && allowed && !excludedIds.has(account.id);
}

export function selectCanaryAccounts(
	accounts: Account[],
	internalIds: ReadonlySet<string>,
	excludedIds: ReadonlySet<string>
): string[] {
	return accounts
		.filter((account) => canReceiveCanary(account, internalIds, excludedIds))
		.map((account) => account.id);
}

const accounts: Account[] = [
	{ id: 'acct-17', active: true, region: 'eu' },
	{ id: 'acct-23', active: true, region: 'us' },
	{ id: 'acct-31', active: false, region: 'eu' },
	{ id: 'acct-42', active: true, region: 'us' },
	{ id: 'acct-58', active: true, region: 'us' }
];

const internalIds = new Set(['acct-23', 'acct-42']);
const excludedIds = new Set(['acct-42']);

console.log(selectCanaryAccounts(accounts, internalIds, excludedIds));
// [ 'acct-17', 'acct-23' ]
GoCanary eligibility · predicate and set membership
rollout.go
package main

import "fmt"

type Account struct {
	ID     string
	Active bool
	Region string
}

// A candidate receives the canary only when active and selected by at least one allow rule,
// while never appearing in the explicit exclusion set. The rules are illustrative policy.
func canReceiveCanary(account Account, internalIDs, excludedIDs map[string]bool) bool {
	allowed := account.Region == "eu" || internalIDs[account.ID]
	return account.Active && allowed && !excludedIDs[account.ID]
}

func selectCanaryAccounts(accounts []Account, internalIDs, excludedIDs map[string]bool) []string {
	selected := make([]string, 0)
	for _, account := range accounts {
		if canReceiveCanary(account, internalIDs, excludedIDs) {
			selected = append(selected, account.ID)
		}
	}
	return selected
}

func main() {
	accounts := []Account{
		{ID: "acct-17", Active: true, Region: "eu"},
		{ID: "acct-23", Active: true, Region: "us"},
		{ID: "acct-31", Active: false, Region: "eu"},
		{ID: "acct-42", Active: true, Region: "us"},
		{ID: "acct-58", Active: true, Region: "us"},
	}
	internalIDs := map[string]bool{"acct-23": true, "acct-42": true}
	excludedIDs := map[string]bool{"acct-42": true}
	fmt.Println(selectCanaryAccounts(accounts, internalIDs, excludedIDs))
	// [acct-17 acct-23]
}
06 / Change the policy

Transfer the method to a deny rule with two independent causes.

Practice / Preview accessA preview is available to verified users on a supported client, unless the account is suspended.
Predicate V
User has verified their email.
Predicate C
Client version is supported.
Predicate S
Account is suspended.
Task
Write the predicate, then test: verified and supported but suspended; verified and unsupported; unverified but supported.
Show a worked answer

The rule is V AND C AND NOT S. The suspended user fails despite satisfying both positive checks. The unsupported client fails because C is false; the unverified user fails because V is false.

Before shipping, clarify what happens if verification or suspension state is unavailable, whether “supported” is computed from a server-controlled version, and how quickly a suspension propagates. The Boolean rule cannot answer those data freshness questions.

Take with you: name the population, define each predicate, group alternatives, apply universal vetoes last, then test the counterexamples the policy implies.