3.3-Logical-Operators
3.3 Logical Operators
Table of Contents
- •Introduction
- •AND Operator (&&)
- •OR Operator (||)
- •NOT Operator (!)
- •Short-Circuit Evaluation
- •Truthy and Falsy Values
- •Logical Assignment Operators
- •Nullish Coalescing (??)
- •Combining Logical Operators
- •Common Patterns
- •Operator Precedence
- •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
| Operator | Name | Description | Example |
|---|---|---|---|
&& | AND | True if both operands are true | a && b |
|| | OR | True if at least one operand is true | a || b |
! | NOT | Negates the boolean value | !a |
?? | Nullish Coalescing | Returns right if left is null/undefined | a ?? 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
| Operator | Returns | Short-Circuits When |
|---|---|---|
a && b | First falsy value or last value | First operand is falsy |
a || b | First truthy value or last value | First operand is truthy |
!a | Boolean opposite | N/A |
a ?? b | b only if a is null/undefined | Left is not null/undefined |
Key Points
- •
&&and||return operand values, not booleans - •Short-circuit evaluation prevents unnecessary computation
- •Use
??when 0, "", or false are valid values - •
!converts to boolean and negates - •Use parentheses to make complex expressions clear
Next Steps
Continue learning operators: