Boolean Logic in JavaScript

Part 1: Boolean Operators & Truth Tables

Nick Gard
7 min readJul 23, 2018

This is part of a series. Read parts 2, 3, and 4.

Photo by Chris Ried on Unsplash

I’m not a computer scientist — I don’t have a CS degree. But I am a mathematician, and that knowledge and skill has been invaluable to me as I learn and do programming. It’s not the statistics or calculus that I end up using daily, but rather my thorough understanding of Boolean logic. There have been many times that I have been able to turn a complex combination of ampersands, pipes, exclamation marks and equal signs into something much simpler and much more readable. I’d like to share some of this knowledge, so I started writing this article. It got really long, so I’ve broken it up into several parts: operators and truth tables (this article), rules of replacement, universal and existential statements, and finally, translating English to logic. I hope this is as beneficial to you as it has been to me. Enjoy!

Truthy & Falsy Values in JavaScript

Before we begin looking at logical expressions, which rely on the truthiness of statements to derive validity of the expression, we should have a solid understanding of what is truthy in JavaScript. Since JavaScript is loosely typed, values can be coerced into booleans to evaluate logical expressions. if conditions, &&, ||, and the part of a ternary statement preceding the question mark (_?_:_) all coerce their evaluated values into booleans. (Note that this doesn’t mean that they necessarily return a boolean from the operation.) The shortcut to knowing what is truthy is to know that there are only six falsy values — false, null, undefined, NaN, 0, and '' — and everything else is truthy. This means that [] and {} are both truthy, which tend to trip people up.

The Logical Operators

In formal propositional logic, there are only a few operators: negation, conjunction, disjunction, implication, and bicondition. These each have JavaScript equivalents: !, &&, ||, if (/* condition */) { /* then consequence */}, and ===, respectively. All other logical statements can be built up from these, including exclusive or (xor) and if-then-else (ternary) statements. We’ll get to those in Part 2.

First, let’s look at the truth tables for each of our basic operators. The truth tables tell us what the truthiness of an expression is based on the truthiness of its parts. For instance, the first row in the Negation truth table (below) should be read like this: “if statement A is True, then the expression !A is False.” Truth tables are important because if two expressions generate the same truth table, then those expressions are equivalent and can replace one another.

The Negation table is very straightforward. Negation is the only unary logical operator, meaning it acts on a single input. This means that !A || B should not be considered the same as !(A || B). Parentheses act like grouping notation similar to what you’d find in mathematics.

Negating a simple statement is not difficult; the negation of “it is raining” is “it is not raining,” and the negation of JavaScript’s primitive true is, of course, false. However, negating complex statements or expressions is not so simple. What is the negation of “it is always raining” or isFoo && isBar? We will cover negating these and similar expressions in the next article.

The Conjunction table shows that the expression A && B is true only if both A and B are true. This should be very familiar from writing JavaScript.

The Disjunction table should also be very familiar. A disjunction (logical OR statement) is true if either or both of A and B is true.

The Implication table is not as familiar. Since A implies B, A being true implies B is true. However, B can be true for reasons other than A, which is why the last two lines of the table are true. The only time implication is false is when A is true and B is false because then A doesn’t imply B.

While if statements are used for implications in JavaScript, not all if statements work this way. Usually, we use if as a flow control, not as a truthiness check where the consequence also matters in the check. Here is the archetypical implication if statement:

function implication(A, B) {
if (A) {
return B;
} else {
/* if A is false, the implication is true */
return true;
}
}

Don’t worry that this is somewhat awkward. There are easier ways to code implications. Because of this awkwardness, though, I will continue to use as the symbol for implications throughout these articles.

The Bicondition operator, sometimes called if-and-only-if (IFF), evaluates to true only if the two operands, A and B, share the same truthiness value. Because of how JavaScript handles comparisons, the use of === for logical purposes should only be used on operands cast to booleans. That is, instead of A === B, we should use !!A === !!B.

The Complete Truth Table

Caveats

There are two big caveats to treating JavaScript code like propositional logic: short circuiting and order of operations.

Short circuiting is something that JavaScript engines do to save time by not evaluating something that will not change the output of the whole expression. The function doSomething() in the following examples is never called because no matter what it returned, the outcome of the logical expression wouldn’t change:

// doSomething() is never called
false && doSomething();
true || doSomething();

Recall that conjunctions (&&) are true only if both statements are true, and disjunctions (||) are false only if both statements are false, so in each of these cases, after reading the first value, no more calculations need to be done to evaluate the logical outcome of the expressions. Because of this feature, JavaScript sometimes breaks logical commutativity. Logically A && B is equivalent to B && A, but you would break your program if you commuted window && window.mightNotExist into window.mightNotExist && window. That’s not to say that the truthiness of a commuted expression is any different, just that JavaScript may throw an error trying to parse it.

JavaScript does not guarantee logical commutativity

The order of operations in JavaScript caught me by surprise because I was not taught that formal logic had an order of operations, other than by grouping and left-to-right. It turns out that many programming languages consider && to have a higher precedence than ||. This means that && is grouped (not evaluated) first, left-to-right, and then || is grouped left-to-right. This means that A || B && C is not evaluated the same way as (A || B) && C, but rather as A || (B && C)¹.

JavaScript does not group boolean operators strictly from left to right

Fortunately, grouping, (), holds the topmost precedence in JavaScript, so we can avoid surprises and ambiguity by manually associating the statements we want evaluated together into discrete expressions. This is why many code linters prohibit having both && and || within the same group.

Calculating Compound Truth Tables

Now that the truthiness of simple statements are known, the truthiness of more complex expressions can be calculated. To begin, count the number of variables in the expression and write a truth table that has 2ⁿ rows. Next create columns for each of the variables and fill them with every possible combination of true/false values. I recommend filling the first half of the first column with T and the second half with F, then quartering the next column and so on until it looks like this:

Then write the expression down and solve it in layers, from the innermost groups outward for each combination of truth values:

Next: Logical Equivalencies.

Notes

1. I would have expected A() || B() && C() to be evaluated the same way as (A() || B()) && C() in the following example, but it is not:

const A = () => {
console.log('A');
return true;
}
const B = () => {
console.log('B');
return true;
}
const C = () => {
console.log('C');
return false;
}
A() || B() && C()
// prints "A", returns true
(A() || B()) && C()
// prints "A C", returns false

--

--