open atlas
↑ Back to track
Logic, from zero LOGIC · 01 · 02

AND, OR, NOT: truth tables, De Morgan, and the art of negating conditions

AND, OR, NOT and XOR are fully defined by tiny truth tables. De Morgan's laws say how negation distributes — NOT (a AND b) becomes (NOT a) OR (NOT b) — and short-circuit evaluation is the truth table running at runtime. Most compound-condition bugs are De Morgan done wrong.

LOGIC Foundations ◷ 17 min
Level
FoundationsJuniorMiddleSenior

The spec was one sentence: “Block the request unless the user is verified and active.” The developer reasoned out loud — block when NOT verified-and-active, so NOT verified AND NOT active — and shipped if (!user.verified && !user.active) block(req). Code review approved it. Three months later a security audit found that a deactivated ex-employee, still email-verified, had kept full API access the whole time. The condition only blocked users failing both checks; failing exactly one sailed through. The fix was a single character, && to ||, and the lesson was two centuries old: when negation moves through AND, the AND must flip to OR.

Goal

After this lesson you can read truth tables for AND, OR, NOT, and XOR; apply both De Morgan’s laws to rewrite a negated compound condition; explain what short-circuit evaluation does; and identify the three equivalent ways to write “block unless verified AND active.”

1

A connective glues propositions into bigger ones, and a truth table is its complete definition. A connective glues propositions into bigger propositions. The compound’s truth value depends only on the truth values of its parts, and a truth table lists every combination. With two inputs there are just four rows:

pqp AND q (&&)p OR q (||)p XOR q
TTTTF
TFFTT
FTFTT
FFFFF

NOT (!) is a one-input table: NOT T is F, NOT F is T. The tables are the whole definition.

2

AND is strict; OR is inclusive; XOR is exclusive. AND is true in exactly one row — when both parts are true. OR is inclusive: true when at least one part is true, including the row where both are. XOR is true when the parts differ. Everyday “or” often means XOR (“soup or salad” — not both), but code’s || is always inclusive OR. There is no XOR keyword in most languages; a !== b on booleans does the job.

3

De Morgan’s laws: when negation moves in, the connective flips. Negating a compound condition is where intuition fails most reliably. Both laws in code form:

!(a && b)  ===  (!a || !b)   // NOT (both)         = at least one fails
!(a || b)  ===  (!a && !b)   // NOT (at least one) = both fail

The pattern: when ! distributes over the parts, the connective flips — AND becomes OR, OR becomes AND. The spec’s bug, fixed three equivalent ways:

if (!(user.verified && user.active)) block(req);   // direct negation
if (!user.verified || !user.active) block(req);    // De Morgan — correct
if (!user.verified && !user.active) block(req);    // BUG: blocks only when BOTH fail
4

Short-circuit evaluation runs the truth table left to right and stops early. Your runtime does not evaluate both sides of && and then consult the table. If the left side of && is false, the result is already F and the right side is never evaluated. If the left side of || is true, the result is already T and the right side is skipped. This is short-circuit evaluation. It lets the left side guard the right against crashes — user && user.name.length > 0 — but any side effect on the right silently does not happen when the left side decides.

Truth tables for the four main connectives
pqp AND qp OR qp XOR qNOT p
TTTTFF
TFFTTF
FTFTTT
FFFFFT
Worked example

Apply De Morgan’s law to rewrite !(loggedIn && hasPaid) without the outer negation.

The condition !(loggedIn && hasPaid) means “NOT (logged-in AND paid).”

De Morgan’s first law says !(a && b) === (!a || !b). Apply it:

!(loggedIn && hasPaid)
// substitute a = loggedIn, b = hasPaid
!loggedIn || !hasPaid

Verify with the mixed row where loggedIn = true, hasPaid = false — a logged-in user who has not paid:

  • Original: !(true && false)!(false)true — correctly blocked.
  • Rewrite: !true || !falsefalse || truetrue — same result.

The wrong version !loggedIn && !hasPaid gives false || falsefalse — this user passes through. That is the bug.

Why this works

Why do languages short-circuit at all, given the trap? Two reasons. Performance: the right side may be expensive (a DB call, a network check), and skipping it when the left side already decides is free speed. Safety: the guard idiom — check for null on the left, dereference on the right — only works because the right side is guaranteed not to run. But the price is that order now matters: a && b and b && a have identical truth tables yet different behavior when one side can crash or has a side effect. Rule of thumb: keep right-hand operands pure (no side effects), and put cheap checks left, expensive ones right.

Practice 0 / 5

What is (T AND F)? Type T or F.

What is (F OR T)? Type T or F.

What is NOT (T AND T)? Type T or F.

Apply De Morgan to !(a || b). Which equals it: (!a && !b) or (!a || !b)?

With user = null, does (user && user.name) crash in JavaScript? Type yes or no.

Check yourself
Quiz

You must negate the condition (loggedIn && hasPaid). Which rewrite is correct?

Recap

Connectives build compound propositions whose truth depends only on the parts, and truth tables are their complete definition: AND is true in exactly one of four rows (both parts true), inclusive OR is false in exactly one (both parts false), XOR is true when the parts differ, NOT swaps the single value. Everyday “or” is often exclusive, but code’s || is inclusive — when “not both” matters, that is XOR, written a !== b on booleans. Negation is governed by De Morgan’s laws: NOT (a AND b) equals (NOT a) OR (NOT b), and NOT (a OR b) equals (NOT a) AND (NOT b) — the connective flips as the negation moves inside. Distributing NOT without flipping is the canonical compound-condition bug; it agrees with the correct condition on the rows that reviews and happy-path tests exercise, and disagrees on the mixed rows that production is full of. Fight it by enumerating the four rows or by naming the positive condition and negating the name. At runtime, the table executes left to right with short-circuiting: a falsy left operand decides AND and skips the right side, a truthy one decides OR — so keep right operands pure and put cheap checks left.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

Apply this

Put this lesson to work on a real build.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.