Mathematics for Engineers · Lesson 1 · 12 minutes

Logic and the conditions you write

A support ticket lands: a suspended account owner can still open the billing page. You read the access check. It looks fine. Everyone who reviews it says it looks fine.

It is not fine, and no amount of staring will show you why. The bug is not in the code — it is in the logic, and logic has a notation that makes this kind of bug visible instead of subtle.

  1. Guess first
  2. The scenario
  3. Into notation
  4. The working
  5. Prove it
  6. Where it bites
  7. Check

The scenario

One line of access control

A billing service decides who may see a company's payment settings. Three facts about the signed-in person are available: whether they are an admin, whether their account is suspended, and whether they are the owner of the company.

The written policy is one sentence: admins and owners can see billing, and suspended accounts can see nothing.

The code that implements it is one line:

if (isAdmin && !isSuspended || isOwner)

Read that line and ask the question the ticket is really asking: is there any combination of those three facts where the code disagrees with the policy?

You could answer it by testing. Sign in as a suspended owner and look. That finds this bug, if you think to try that exact combination — and tells you nothing about the seven other combinations you did not try. There is a better way to answer, and it is the whole of this lesson: stop reading the condition as instructions and start reading it as a formula.

Converting the scenario

From code to notation, in three moves

Move one: name the facts. Each of the three is something that is either true or false, with nothing in between. In logic that is called a proposition, and it gets a single letter:

The three propositions
LetterPropositionIn the code
Athis person is an adminisAdmin
Sthis account is suspendedisSuspended
Othis person is the ownerisOwner

Move two: translate the operators. Three symbols cover everything a boolean condition can do.

The three connectives
CodeSymbolNameTrue when
&&conjunction (and)both sides are true
||disjunction (or)at least one side is true
!¬negation (not)the thing after it is false

Move three — and this is the one that carries the bug: put the brackets in. The code has none, so the language supplies them from its precedence rules, and && binds tighter than ||. That is not a quirk of JavaScript. It is the same convention as arithmetic, where a+b×c means a+(b×c): is logic's multiplication and is its addition.

So the line the machine actually runs is:

(A¬S) O

and not the reading most people's eyes give it on a quick pass:

A (¬SO)

Those two formulas are different, and the difference is the ticket. In the first, O sits outside everything — being the owner grants access on its own, with suspension never consulted. In the second, being an admin is required no matter what.

The working

Answering the ticket, one line at a time

The question is whether a suspended owner gets in. In notation: what does (A¬S)O evaluate to when S is true and O is true? Take the worst case for access — the person is not an admin — and substitute.

  1. (A¬S)O

    Start. The formula, with the brackets the language put in.

  2. (false¬true)true

    Substitute the case from the ticket: a suspended owner who is not an admin. A false, S true, O true.

  3. (falsefalse)true

    Negation. ¬true is false.

  4. falsetrue

    Conjunction. needs both sides true; one false side is enough to make the whole bracket false.

  5. true

    Disjunction. needs only one true side. Access granted. The ticket is real, and now you know exactly why: the owner clause sits outside the suspension check, so for an owner the suspension flag is never read at all.

Five lines, each one a rule you could name. That is what "doing the maths" buys you over reading the code harder: every step has a reason, so the conclusion is checkable by someone who does not trust you.

But one substitution answers one question. The ticket asked about a suspended owner. What about the other seven combinations?

The interactive bit

Prove it: the equivalence checker

Here is the move that makes boolean logic different from almost every other kind of maths you will meet: the space of possibilities is finite, and it is small. Three propositions, each true or false, gives 23=8 combinations. Not "too many to check" — eight.

So you never have to wonder whether two conditions behave the same. Write out all eight rows of each and compare. If every row agrees, they are equivalent, and that is a proof, not a sample. If any row disagrees, you have found the input that separates them, which is also your test case.

Pick any two of the candidate conditions below. The table enumerates every combination and marks the rows where they part company.

Choose two conditions to compare.
Every combination of the three propositions
A
admin
S
suspended
O
owner
First Second Agree?

Three comparisons are worth making before you move on.

1 · The precedence trap

Compare the condition as written with the bracketing most people read. They differ on two rows — and in both of them the person is a non-admin owner, which is not a hypothetical: it is the founder who handed the admin role to someone else. Two conditions that read like the same sentence are not the same function.

2 · A rewrite that is genuinely safe

Compare the condition as written with the De Morgan rewrite. Every row agrees, so the rewrite is safe to merge — and you know that without running the application once.

3 · The fix

Compare the condition as written with the policy as written. The rows where they differ are exactly the bug: suspended owners. That difference set is the bug report and the regression test, in one.

Two laws worth memorising

De Morgan, and the contrapositive

De Morgan's laws — how to push a ¬ inwards

Sooner or later you need the opposite of a compound condition: an early-return guard, an inverted filter, a negated database predicate. The rule for negating and is not the obvious one.

¬(PQ) ¬P¬Q ¬(PQ) ¬P¬Q

The negation flips the connective. "Not (both)" is "either one is missing". "Not (either)" is "both are missing". The mistake — and it is the single most common bug in a rewritten condition — is to negate each side and leave the operator alone: ¬(PQ) is not ¬P¬Q. The checker above carries exactly that slip as an option — compare the condition as written with the De Morgan slip and watch two rows come apart. Note which two: they are the rows where nobody is the owner, which is to say the ordinary accounts, not the exotic ones. A slip like this does not announce itself on the edge cases you thought to test.

The is doing real work in those lines. It does not mean "is roughly like". It means every row of the truth table matches — the same standard the checker applies.

Implication, and the only rearrangement of it that is valid

The policy sentence was a conditional: if the account is suspended, access is denied. Written out, with G for "access granted":

S¬G

An implication can be turned around exactly one way and stay true. Swap the two sides and negate both, and you get the contrapositive, which is always equivalent to the original:

G¬S

In words: if access was granted, the account was not suspended. And that is a far more useful sentence than the one the policy started with, because it is directly checkable — it names a property that must hold of every row where the condition came out true. Scan the table above for a row where the condition is true and S is true, and if you find one the policy is violated. You will find one.

The two rearrangements that are not valid are worth naming, because both turn up in code review dressed as reasoning:

  • The converse, ¬GS — "access was denied, so they must be suspended". Not implied: access can be denied for being neither admin nor owner.
  • The inverse, ¬SG — "not suspended, so they get in". Not implied either, and it is how a permission check quietly becomes a permission grant.

Where this bites

The same maths, four places you have already been

The one thing to keep

A condition is a formula, and formulas can be proved equal

You do not have to believe that two conditions behave the same, and you do not have to test your way to confidence. With n booleans there are 2n rows, and for the handful of inputs a real condition has, writing them all out is a minute's work that settles the question completely.

The next lesson takes the same move — replace a vague description with a structure that can be reasoned about — and points it at data instead of control flow: sets, relations, and why a SQL join is an operation you already know.