Lesson 26 of 54

Control Flow and Logic

Boolean Logic Clarity

Boolean logic looks simple. true, false, &&, ||, !. What could go wrong?

Everything. Boolean expressions are where developers make the most logical errors. Off-by-one errors in loops are famous, but off-by-one errors in boolean logic are just as common and harder to spot.

De Morgan's Laws

These two equivalences are essential:

Text
!(a && b) === !a || !b
!(a || b) === !a && !b

Use whichever form reads more naturally.

Example: Negating a Compound Condition

Javascript
// Original
if (!(user.isAdmin && user.hasPermission)) {
  deny();
}

// Apply De Morgan: reads more naturally
if (!user.isAdmin || !user.hasPermission) {
  deny();
}

Example: Simplifying Double Negation

Javascript
// Confusing
if (!(!hasAccess || isBlocked)) { }

// Apply De Morgan: !(A || B) = !A && !B
// So !(!hasAccess || isBlocked) = hasAccess && !isBlocked
if (hasAccess && !isBlocked) { }

Breaking Complex Booleans

When a boolean expression gets complex, break it into named parts.

Before: Wall of Conditions

Javascript
if (
  order.status === 'confirmed' &&
  order.paymentReceived &&
  inventory.hasStock(order.items) &&
  !order.isHeld &&
  (order.shippingAddress.country === 'US' || hasInternationalShipping) &&
  order.createdAt > cutoffDate
) {
  shipOrder(order);
}

You have to read the entire expression to understand when shipping happens.

After: Named Conditions

Javascript
const isConfirmedAndPaid = order.status === 'confirmed' && order.paymentReceived;
const hasInventory = inventory.hasStock(order.items);
const isNotHeld = !order.isHeld;
const canShipToDestination = order.shippingAddress.country === 'US' || hasInternationalShipping;
const isWithinShippingWindow = order.createdAt > cutoffDate;

const isReadyToShip = 
  isConfirmedAndPaid && 
  hasInventory && 
  isNotHeld && 
  canShipToDestination && 
  isWithinShippingWindow;

if (isReadyToShip) {
  shipOrder(order);
}

Now each condition is documented by its name.

Truth Tables for Debugging

When boolean logic is confusing, write a truth table.

The Problem

Javascript
// This is buggy. Can you spot why?
const canAccess = (isAdmin || isMember) && !isExpired && !isBanned;

Is this right? Let's check with a truth table:

isAdminisMemberisExpiredisBannedcanAccessExpected
truefalsefalsefalsetrue ✓true
falsetruefalsefalsetrue ✓true
truetruetruefalsefalse ✓false
falsefalsefalsefalsefalse ✓false
truetruefalsetruefalse ✓false

The logic is correct. But writing the truth table forces you to think through edge cases.

Finding a Bug

Javascript
// Someone wrote this for "allow if user is premium OR if content is free"
const canView = user.isPremium || content.isFree && !content.isRestricted;

Wait—what's the precedence? && binds tighter than ||, so this is:

Javascript
user.isPremium || (content.isFree && !content.isRestricted)

Truth table:

isPremiumisFreeisRestrictedcanViewIntended
truefalsetruetruetrue ✓
falsetruefalsetruetrue ✓
falsetruetruefalse???

Is it intended that free-but-restricted content is blocked? Maybe. The truth table surfaces the question.

Avoid Nested Ternaries

Nested ternary operators create boolean puzzles:

Javascript
// DON'T
const access = isAdmin ? 'full' : isMember ? 'limited' : isGuest ? 'read-only' : 'none';

This is hard to parse. Use if-else or a lookup:

Javascript
// DO
function getAccessLevel(user) {
  if (user.isAdmin) return 'full';
  if (user.isMember) return 'limited';
  if (user.isGuest) return 'read-only';
  return 'none';
}

// OR
const ACCESS_LEVELS = {
  admin: 'full',
  member: 'limited',
  guest: 'read-only',
};

const access = ACCESS_LEVELS[user.role] ?? 'none';

Short-Circuit Evaluation

JavaScript's && and || short-circuit. This is useful but can be confusing.

Common Pattern: Default Values

Javascript
const name = user.name || 'Anonymous';
const config = options.config || {};

But watch out for falsy values:

Javascript
const count = item.count || 10;  // Bug if count is 0!

Use nullish coalescing (??) instead:

Javascript
const count = item.count ?? 10;  // Only defaults if null/undefined

Common Pattern: Conditional Execution

Javascript
// Only call if user exists
user && user.save();

// Better with optional chaining
user?.save();

When Short-Circuit Is Confusing

Javascript
// What does this return?
const result = a || b && c;

// Answer: depends on values AND precedence
// && binds tighter: a || (b && c)

When precedence isn't obvious, use parentheses:

Javascript
const result = a || (b && c);  // Intent is clear

Simplification Rules

Some patterns can always be simplified:

PatternSimplifies To
!!valueBoolean(value) or just use in boolean context
x === truex
x === false!x
x ? true : falseBoolean(x) or !!x
x ? false : true!x
x && xx
`x
x && truex
`x

Testing Boolean Logic

Complex boolean logic needs tests for each branch:

Javascript
describe('isEligibleForDiscount', () => {
  test('returns true for premium members with enough orders', () => {
    expect(isEligibleForDiscount(premiumUserWithOrders)).toBe(true);
  });
  
  test('returns false for standard members', () => {
    expect(isEligibleForDiscount(standardUser)).toBe(false);
  });
  
  test('returns false for premium members with recent refunds', () => {
    expect(isEligibleForDiscount(premiumWithRefund)).toBe(false);
  });
  
  // Test each condition explicitly
});

Key insight: Boolean expressions are deceptively complex. Use De Morgan's laws to find clearer forms. Break complex expressions into named parts. When confused, draw a truth table. Test every branch.