Thinking in Logic: A Programmer's Guide From Code to Reason

Chapter 4: Chapter 4 — Propositions and Operators

By BadRobot • 1460 words • Nov 13, 2025 • Updated Nov 13, 2025

When you strip programming down to its most basic level — past the syntax, the types, and the frameworks — what’s left is logic.

Every if, every while, every test case, and every validation rule you’ve ever written is, at its core, a logical statement being checked for truth or falsity.

That’s where our next foundation starts: the proposition.

---

4.1 What a Proposition Actually Is

A proposition is a statement that can be true or false, but not both.

That sounds trivial, but it’s an important constraint. A statement like “the system is loading” only becomes a proposition once we can assign it a truth value — for example, system.loading == true.

By contrast, something like “load faster” isn’t a proposition at all. It’s an imperative. Logic ignores commands, questions, and feelings. It only deals in statements of fact (or assumed fact).

In programming, propositions are everywhere:

isLoggedIn === true
age >= 18
status !== 'error'

Each of these is a truth claim — a mini logical test the program uses to decide what to do next.

You can think of a program as a long chain of propositions, each asking:

> “Given the current state, what is true right now?”

---

4.2 Connectives: The Grammar of Logic

In English, you combine statements with words like “and,” “or,” and “not.”
In code, you use logical operators:

Operator Symbol Meaning Example

AND && Both must be true isAdmin && isLoggedIn
OR ` `
NOT ! Negates truth !isSuspended
XOR varies (^ or custom) True only if one side is true, not both `(A

These connectives are the glue between propositions. They form larger statements, which your code executes in fractions of a millisecond — but which you, as the engineer, must design and interpret.

Let’s unpack how they behave, because this is where subtle reasoning mistakes begin.

---

4.3 Truth Tables: Seeing the Structure

A truth table is the most compact way to show how logical operators behave. You don’t need to memorize them — you just need to understand their shape.

Let’s take the simplest: AND.

A B A && B

T T T
T F F
F T F
F F F

That means: for A && B to be true, both sides must be true.

You can read that mentally as: “only if everything checks out.”

The pattern for OR is almost the opposite:

A B A || B

T T T
T F T
F T T
F F F

That reads: “at least one of these conditions passes.”

Most bugs involving conditionals come down to confusing those two — using && when you meant ||, or vice versa.

It sounds trivial, but even senior engineers slip here because they jump ahead in their heads, assuming they know what the code “means” instead of what it actually says.

Practical habit:

> Read compound conditions out loud before trusting them.
“This should execute only if the user is logged in and verified.”
Then check that your code actually uses &&.

---

4.4 Negation: The Source of Subtle Bugs

Negation — ! — seems simple, but in practice it’s where logic breaks down fastest.

Consider:

if (!user.isAdmin && !user.isVerified)

What does that really mean?

To a tired mind, it might sound like “if the user is not an admin or not verified.” But it’s not. It means “if the user is neither admin nor verified.”

Small difference, big impact.

This is where De Morgan’s Laws become indispensable:

> !(A && B) is equivalent to (!A || !B)
!(A || B) is equivalent to (!A && !B)

If that looks like symbol soup, think of it like a negative sentence in English:

“Not (A and B)” → “Either not A or not B.”

“Not (A or B)” → “Both not A and not B.”

Practical example:

if (!(user.isAdmin || user.isModerator)) {
denyAccess();
}
// Equivalent:
if (!user.isAdmin && !user.isModerator) {
denyAccess();
}

When conditions get complex, write them in plain language first, then back into code. The mental translation exercise forces you to confirm your logic rather than assume it.

---

4.5 Translating English to Logic and Back

Programmers often describe system behavior in plain language during planning, then translate it into conditionals later. That translation process is where reasoning gets sloppy.

Here’s a concrete workflow:

1. Write the rule plainly.

> “A user can post only if they’re logged in and their account is active.”

2. Identify propositions.

isLoggedIn

isActive

3. Combine them logically.

if (isLoggedIn && isActive) {
allowPost();
}

4. Verify with counterexamples.
If either is false, should posting fail? Yes → good.
If both are false, should posting fail? Yes → consistent.

Now take a more ambiguous rule:

> “A user can comment if they are logged in or the thread is public.”

That translates to:

if (isLoggedIn || thread.isPublic)

But what if the thread is private and the user isn’t logged in? Should they see the “Add Comment” button? Maybe not. Suddenly, the logical condition touches design. This is where logical thinking merges with practical reasoning — clarity in one prevents confusion in the other.

---

4.6 Common Logical Fallacies in Code

Logical fallacies aren’t just philosophical mistakes — they appear constantly in software.

Let’s look at a few:

1. The Inverse Fallacy

You assume the reverse of a true statement is also true.

> If A → B, then B → A.
Not necessarily.

Example:

if (user.isAdmin) showAdminPanel();

That doesn’t mean every user who sees the admin panel is an admin. Maybe the UI has a bug.

Don’t treat code behavior as proof of logical truth. Treat it as evidence.

2. The Default Fallacy

You assume the “else” branch means the opposite of the “if.”

if (user.isVerified) sendWelcomeGift();
else denyAccess();

Here, non-verified users are denied access — but maybe unverified just means “pending.” Logic isn’t binary unless you define it that way.

3. The Overloaded OR

You cram too many dissimilar conditions together:

if (isHoliday || user.isAdmin || item.inStock)

Each term belongs to a different domain — time, privilege, inventory. When they mix, reasoning stops being local and becomes opaque.

Fix: split it up. Local logic is legible logic.

---

4.7 Short-Circuit Evaluation and Real-World Consequences

In many languages, logical operators are short-circuited.

That means:

In A && B, if A is false, B isn’t even checked.

In A || B, if A is true, B isn’t checked.

This isn’t just a performance detail — it’s a reasoning rule.

If the second expression has side effects, your program’s behavior depends on whether the first expression ran.

Example:

if (user && user.isAdmin) { ... }

If user is null, the user.isAdmin part never runs — saving you a runtime error. That’s good.

But:

if (isTest || runMigration()) ...

Here, runMigration() will only execute if isTest is false — a hidden dependency in the logic.

Rule of thumb:

> Never rely on logical operators for control flow unless you explicitly intend to.

Logic expresses relationships between facts — not sequencing of actions. Keep those concerns separate when you can.

---

4.8 How to Practice Logical Literacy

You can train yourself to think in propositions and operators through deliberate micro-habits:

1. Rewrite complex conditionals as tables.
List each variable and the outcome. You’ll instantly see inconsistencies.

2. Predict outcomes mentally.
Before running a condition, say what you expect to happen for each case.

3. Refactor for readability, not cleverness.
Break compound conditions into named helper functions:

const canPost = isLoggedIn && isActive;
if (canPost) ...

A named predicate communicates logic in English, reducing cognitive load.

4. Explain your condition to another person.
If you can’t explain it clearly, it’s too complex — or logically unstable.

These are small habits, but they have big cumulative effects. Over time, they replace fuzzy guessing with deliberate clarity.

---

4.9 Logic as the Skeleton of Control

Every conditional statement, loop, and function return rests on these fundamentals.

When you understand propositions and operators deeply, you stop writing code by “feel” and start shaping its behavior intentionally.

The next time you face a confusing bug, try this: instead of thinking “why isn’t this working,” think “what propositions are being tested here, and which of them might be false?”

That shift — from behavior to truth structure — is the beginning of systematic reasoning.

---

Summary of Chapter 4:

Propositions are truth-valued statements — the atoms of logic.

Logical operators (&&, ||, !) form structured relationships between propositions.

Use truth tables to visualize and verify compound conditions.

Translate plain rules to logic (and back) carefully — most bugs appear during that translation.

Avoid fallacies: don’t assume the reverse of a statement or conflate unrelated conditions.

Short-circuit evaluation can change behavior; understand when it happens.

Practice by externalizing, naming, and explaining logical conditions.

Comments (0)

No comments yet. Be the first to share your thoughts!

Login to leave a comment