Docs

3.3-Logical-Operators

3.3 Logical Operators

Table of Contents

  1. Introduction
  2. AND Operator (&&)
  3. OR Operator (||)
  4. NOT Operator (!)
  5. Short-Circuit Evaluation
  6. Truthy and Falsy Values
  7. Logical Assignment Operators
  8. Nullish Coalescing (??)
  9. Combining Logical Operators
  10. Common Patterns
  11. Operator Precedence
  12. Best Practices

Introduction

Logical operators work with boolean values and are used to combine or modify conditions. They are essential for control flow and conditional expressions.

Quick Reference

OperatorNameDescriptionExample
&&ANDTrue if both operands are truea && b
||ORTrue if at least one operand is truea || b
!NOTNegates the boolean value!a
??Nullish CoalescingReturns right if left is null/undefineda ?? b

AND Operator (&&)

Basic Behavior

The AND operator returns true only if BOTH operands are truthy.

   AND TRUTH TABLE

   A       B       A && B
   ──────────────────────
   true    true    true
   true    false   false
   false   true    false
   false   false   false

Basic Examples

console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false

With Non-Boolean Values

The && operator doesn't just return boolean - it returns one of its operands:

// Returns FIRST falsy value, or LAST value if all truthy
console.log('hello' && 'world'); // "world" (both truthy, return last)
console.log(0 && 'world'); // 0 (first is falsy, return it)
console.log('hello' && 0); // 0 (second is falsy, return it)
console.log(null && 'hello'); // null (first is falsy)
console.log('hello' && undefined); // undefined (second is falsy)

Practical Uses

// Conditional execution
let user = { name: 'John', isAdmin: true };
user.isAdmin && console.log('Welcome, admin!');

// Guard clause
let data = { items: [1, 2, 3] };
let firstItem = data && data.items && data.items[0]; // 1

// Modern alternative: Optional chaining
let firstItem2 = data?.items?.[0]; // 1

OR Operator (||)

Basic Behavior

The OR operator returns true if AT LEAST ONE operand is truthy.

   OR TRUTH TABLE

   A       B       A || B
   ──────────────────────
   true    true    true
   true    false   true
   false   true    true
   false   false   false

Basic Examples

console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false

With Non-Boolean Values

The || operator returns the first truthy value, or the last value if all falsy:

// Returns FIRST truthy value, or LAST value if all falsy
console.log('hello' || 'world'); // "hello" (first truthy)
console.log(0 || 'world'); // "world" (first falsy, second truthy)
console.log('' || 0 || null); // null (all falsy, return last)
console.log(null || undefined || 'default'); // "default"

Default Values Pattern

// Providing default values
function greet(name) {
  name = name || 'Guest';
  console.log('Hello, ' + name);
}

greet('John'); // Hello, John
greet(); // Hello, Guest
greet(''); // Hello, Guest (⚠️ empty string is falsy!)

// Modern alternative: Default parameters
function greet2(name = 'Guest') {
  console.log('Hello, ' + name);
}

// Or nullish coalescing
function greet3(name) {
  name = name ?? 'Guest';
  console.log('Hello, ' + name);
}

NOT Operator (!)

Basic Behavior

The NOT operator negates a boolean value:

console.log(!true); // false
console.log(!false); // true

With Non-Boolean Values

First converts to boolean, then negates:

// Falsy values become true
console.log(!0); // true
console.log(!''); // true
console.log(!null); // true
console.log(!undefined); // true
console.log(!NaN); // true

// Truthy values become false
console.log(!1); // false
console.log(!'hello'); // false
console.log(!{}); // false
console.log(![]); // false

Double NOT (!!)

Converts any value to its boolean equivalent:

// Convert to boolean
console.log(!!'hello'); // true
console.log(!!1); // true
console.log(!!{}); // true
console.log(!![]); // true

console.log(!!''); // false
console.log(!!0); // false
console.log(!!null); // false
console.log(!!undefined); // false

// Same as Boolean()
console.log(Boolean('hello')); // true
console.log(Boolean('')); // false

Short-Circuit Evaluation

What is Short-Circuiting?

Logical operators evaluate from left to right and stop as soon as the result is determined:

   SHORT-CIRCUIT BEHAVIOR

   AND (&&):
   ┌─────────────────────────────────────┐
   │ If first operand is FALSY:          │
   │   → Return first operand            │
   │   → Second operand NOT evaluated    │
   │                                      │
   │ If first operand is TRUTHY:         │
   │   → Return second operand           │
   └─────────────────────────────────────┘

   OR (||):
   ┌─────────────────────────────────────┐
   │ If first operand is TRUTHY:         │
   │   → Return first operand            │
   │   → Second operand NOT evaluated    │
   │                                      │
   │ If first operand is FALSY:          │
   │   → Return second operand           │
   └─────────────────────────────────────┘

AND Short-Circuit

function expensive() {
  console.log('Expensive function called!');
  return true;
}

// Function NOT called - short-circuited
false && expensive(); // Returns false, function not called

// Function IS called
true && expensive(); // "Expensive function called!", returns true

OR Short-Circuit

function expensive() {
  console.log('Expensive function called!');
  return true;
}

// Function NOT called - short-circuited
true || expensive(); // Returns true, function not called

// Function IS called
false || expensive(); // "Expensive function called!", returns true

Practical Uses

// Conditional function execution
let isLoggedIn = true;
isLoggedIn && showDashboard();

// Default value (with fallback)
let username = userInput || 'Anonymous';

// Guard against undefined
let length = arr && arr.length;

// Conditional property access
let city = user && user.address && user.address.city;
// Modern: user?.address?.city

Truthy and Falsy Values

Falsy Values (Complete List)

// These are ALL the falsy values in JavaScript:
false; // Boolean false
0 - // Number zero
  0; // Negative zero
0n; // BigInt zero
(''); // Empty string
null; // Null
undefined; // Undefined
NaN; // Not a Number

Truthy Values

Everything else is truthy, including:

// Some surprising truthy values:
true        // Obviously
1           // Any non-zero number
-1          // Negative numbers too
"0"         // String "0" (not empty!)
"false"     // String "false"
[]          // Empty array
{}          // Empty object
function(){} // Any function
Infinity    // Infinity
-Infinity   // Negative infinity
new Date()  // Date object

Common Mistakes

// Empty array is truthy!
if ([]) {
  console.log('This runs!'); // Runs because [] is truthy
}

// Check array length instead
if ([].length) {
  console.log('Array has items');
}

// Empty object is truthy!
if ({}) {
  console.log('This runs!'); // Runs because {} is truthy
}

// Check for keys instead
if (Object.keys({}).length) {
  console.log('Object has keys');
}

// String "0" is truthy!
console.log('0' ? 'truthy' : 'falsy'); // "truthy"

Logical Assignment Operators

AND Assignment (&&=)

Assigns only if the left operand is truthy:

let a = 1;
a &&= 5; // a = 5 (1 is truthy, so assign 5)

let b = 0;
b &&= 5; // b = 0 (0 is falsy, so don't assign)

// Equivalent to:
// a && (a = 5);
// a = a && 5; // Not exactly equivalent!

OR Assignment (||=)

Assigns only if the left operand is falsy:

let a = 0;
a ||= 5; // a = 5 (0 is falsy, so assign 5)

let b = 1;
b ||= 5; // b = 1 (1 is truthy, so don't assign)

// Perfect for defaults
let config = {};
config.timeout ||= 3000;
config.retries ||= 3;

Nullish Assignment (??=)

Assigns only if the left operand is null or undefined:

let a = null;
a ??= 5; // a = 5 (null, so assign)

let b = 0;
b ??= 5; // b = 0 (0 is not nullish, so don't assign)

let c = '';
c ??= 'default'; // c = "" (empty string is not nullish)

// Perfect for optional values that could be 0 or ""
let settings = { volume: 0, name: '' };
settings.volume ??= 50; // stays 0
settings.name ??= 'User'; // stays ""

Nullish Coalescing (??)

vs OR Operator

The ?? operator only treats null and undefined as "empty":

// OR treats all falsy values as "empty"
console.log(0 || 'default'); // "default"
console.log('' || 'default'); // "default"
console.log(false || 'default'); // "default"

// ?? only treats null/undefined as "empty"
console.log(0 ?? 'default'); // 0
console.log('' ?? 'default'); // ""
console.log(false ?? 'default'); // false
console.log(null ?? 'default'); // "default"
console.log(undefined ?? 'default'); // "default"

When to Use Which

// Use || when ANY falsy value should trigger default
let input = getUserInput() || 'Not provided';

// Use ?? when 0, "", or false are valid values
let volume = settings.volume ?? 50; // 0 is valid
let name = settings.name ?? 'Guest'; // "" might be valid
let enabled = settings.enabled ?? true; // false is valid

Cannot Mix with && or || Without Parentheses

// SyntaxError! Cannot mix without parentheses
// let x = a || b ?? c;
// let y = a && b ?? c;

// Must use parentheses
let x = (a || b) ?? c;
let y = a || (b ?? c);

Combining Logical Operators

Operator Precedence

   PRECEDENCE (high to low)

   1. !   (NOT)        - highest
   2. &&  (AND)
   3. ||  (OR)
   4. ??  (Nullish)    - lowest

Examples

// NOT has highest precedence
console.log(!true || false); // false ((!true) || false = false || false)
console.log(!true && false); // false ((!true) && false = false && false)

// AND before OR
console.log(true || (false && false)); // true  (true || (false && false))
console.log((true || false) && false); // false

// Complex example
let a = true,
  b = false,
  c = true;
console.log(a || (b && c)); // true (a || (b && c))
console.log((a || b) && c); // true ((true) && true)

Grouping with Parentheses

// Always use parentheses for clarity
let isValid = (age >= 18 && hasLicense) || isExempt;

// Without parentheses - harder to read
let isValid2 = (age >= 18 && hasLicense) || isExempt;

// Even clearer - break into variables
let meetsRequirements = age >= 18 && hasLicense;
let isValid3 = meetsRequirements || isExempt;

Common Patterns

Default Parameter Pattern

// Old way
function greet(name) {
  name = name || 'Guest';
  return 'Hello, ' + name;
}

// Better for values that could be 0 or ""
function setVolume(level) {
  level = level ?? 50;
  return level;
}

// Modern: Default parameters
function greet(name = 'Guest') {
  return 'Hello, ' + name;
}

Guard Clause Pattern

// Prevent errors when accessing nested properties
function getCity(user) {
  return user && user.address && user.address.city;
}

// Modern: Optional chaining
function getCity(user) {
  return user?.address?.city;
}

Conditional Execution

// Execute only if condition is true
isAdmin && deleteUser(userId);

// With multiple conditions
isAdmin && hasPermission && deleteUser(userId);

// Clearer alternative
if (isAdmin && hasPermission) {
  deleteUser(userId);
}

Toggle Pattern

let isActive = true;

// Toggle boolean
isActive = !isActive; // false
isActive = !isActive; // true

Existence Check

// Check if value exists
function processItem(item) {
  if (!item) {
    console.log('Item is required');
    return;
  }
  // Process item...
}

// With array check
function processItems(items) {
  if (!items || !items.length) {
    console.log('No items to process');
    return;
  }
  // Process items...
}

Fallback Chain

// Try multiple sources for a value
let value = primary || secondary || tertiary || 'default';

// With nullish coalescing
let config = userConfig ?? systemConfig ?? defaultConfig;

Operator Precedence

Complete Precedence Table (Logical Operators)

| Precedence | Operator | Description | | ---------- | ------------------------ | ------------------- | --------- | ---------- | | 1 | ! | Logical NOT | | 2 | <, >, <=, >= | Comparison | | 3 | ==, !=, ===, !== | Equality | | 4 | && | Logical AND | | 5 | | | | Logical OR | | 6 | ?? | Nullish coalescing | | 7 | ? : | Ternary conditional | | 8 | =, +=, &&=, | | =, ??= | Assignment |

Example

// This expression:
let result = (a > 5 && b < 10) || c === 0;

// Is evaluated as:
let result = (a > 5 && b < 10) || c === 0;

Best Practices

1. Use Parentheses for Clarity

// Hard to read
let valid = (a && b) || (c && d);

// Clear
let valid = (a && b) || (c && d);

2. Prefer Nullish Coalescing for Defaults

// ❌ Might incorrectly override 0 or ""
let count = input || 10;

// ✅ Only overrides null/undefined
let count = input ?? 10;

3. Avoid Complex Short-Circuit Logic

// ❌ Hard to read and maintain
(result && process() && save()) || handleError();

// ✅ Clear and readable
if (result) {
  if (process()) {
    save();
  } else {
    handleError();
  }
} else {
  handleError();
}

4. Use Double NOT for Boolean Conversion

// ❌ Verbose
let hasItems = Boolean(array && array.length);

// ✅ Concise
let hasItems = !!(array && array.length);

5. Use Optional Chaining Instead of &&

// ❌ Old pattern
let city = user && user.address && user.address.city;

// ✅ Modern
let city = user?.address?.city;

Summary

OperatorReturnsShort-Circuits When
a && bFirst falsy value or last valueFirst operand is falsy
a || bFirst truthy value or last valueFirst operand is truthy
!aBoolean oppositeN/A
a ?? bb only if a is null/undefinedLeft is not null/undefined

Key Points

  1. && and || return operand values, not booleans
  2. Short-circuit evaluation prevents unnecessary computation
  3. Use ?? when 0, "", or false are valid values
  4. ! converts to boolean and negates
  5. Use parentheses to make complex expressions clear

Next Steps

Continue learning operators:

.3 Logical Operators - JavaScript Tutorial | DeepML