JavaScript Tutorial

JavaScript Logical Operators: Complete Guide with Examples

Master JavaScript logical operators including AND (&&), OR (||), NOT (!), and nullish coalescing (??). Learn short-circuit evaluation, truth tables, and practical patterns with examples.

Welcome back! 👋 In the previous lesson, you learned about comparison operators. Now, let's explore logical operators - operators that work with boolean values and enable you to build complex conditions.

Logical operators are essential for combining multiple conditions, implementing business logic, and controlling program flow. They're the building blocks of decision-making in your code!


What are Logical Operators?

Logical operators are used to combine or invert boolean expressions. They help you create complex conditions by combining simpler ones.

JavaScript provides four main logical operators:

  • && - Logical AND
  • || - Logical OR
  • ! - Logical NOT
  • ?? - Nullish Coalescing (ES2020)
let age = 25;
let hasLicense = true;

// Combining conditions with logical operators
if (age >= 18 && hasLicense) {
  console.log("Can drive");  // Both conditions must be true
}

if (age >= 65 || age < 18) {
  console.log("Special discount");  // At least one must be true
}

if (!hasLicense) {
  console.log("Cannot drive");  // Inverts the boolean
}

Logical AND (&&)

The logical AND operator returns true if both operands are true. Otherwise, it returns false.

Syntax

operand1 && operand2

Truth Table

operand1  operand2  result
───────────────────────────
true      true      true
true      false     false
false     true      false
false     false     false

Rule: Both must be true for the result to be true.

Basic Examples

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

// One or both false
console.log(true && false);    // false
console.log(false && true);    // false
console.log(false && false);   // false

// With comparison operators
console.log(5 > 3 && 10 > 8);  // true (both true)
console.log(5 > 3 && 10 < 8);  // false (second is false)
console.log(5 < 3 && 10 > 8);  // false (first is false)

Real-World Examples

// Age and license check
let age = 20;
let hasLicense = true;

if (age >= 18 && hasLicense) {
  console.log("Can drive");
} else {
  console.log("Cannot drive");
}

// Multiple conditions
let username = "john";
let password = "secret123";
let isActive = true;

if (username !== "" && password !== "" && isActive) {
  console.log("Login successful");
} else {
  console.log("Login failed");
}

// Range check
let score = 85;

if (score >= 80 && score <= 100) {
  console.log("Grade: B or higher");
}

// Form validation
let email = "user@example.com";
let agreedToTerms = true;

if (email.includes("@") && email.includes(".") && agreedToTerms) {
  console.log("Valid registration");
}

Chaining Multiple AND Operators

let a = true;
let b = true;
let c = true;
let d = false;

// All must be true
console.log(a && b && c);        // true
console.log(a && b && c && d);   // false (one is false)

// Practical example
let hasName = true;
let hasEmail = true;
let hasPassword = true;
let hasAge = true;

if (hasName && hasEmail && hasPassword && hasAge) {
  console.log("All required fields filled");
}

Short-Circuit Evaluation with AND (&&)

Important concept: The AND operator uses short-circuit evaluation. If the first operand is falsy, it immediately returns that value without evaluating the second operand.

How Short-Circuit Works

// If first is false, second is never evaluated
false && console.log("This never runs");  // Doesn't log anything

// If first is true, second is evaluated
true && console.log("This runs");  // Logs "This runs"

Short-Circuit Returns the Value

Key Point: && doesn't always return true or false. It returns the first falsy value or the last value if all are truthy.

// Returns first falsy value
console.log(0 && "hello");         // 0 (first falsy)
console.log("" && "hello");        // "" (first falsy)
console.log(null && "hello");      // null (first falsy)
console.log(false && "hello");     // false (first falsy)

// Returns last value if all truthy
console.log("hello" && "world");   // "world" (last value)
console.log(5 && 10);              // 10 (last value)
console.log("test" && 42);         // 42 (last value)
console.log(true && "yes");        // "yes" (last value)

// Multiple values
console.log(5 && 10 && 15);        // 15 (last value)
console.log(5 && 0 && 15);         // 0 (first falsy)
console.log(5 && 10 && "");        // "" (first falsy)

Practical Uses of Short-Circuit AND

1. Conditional Execution:

// Execute function only if condition is true
let isLoggedIn = true;
isLoggedIn && console.log("Welcome back!");  // Logs if true

// Calling methods safely
let user = { name: "John", greet: function() { console.log("Hi!"); } };
user && user.greet();  // Only calls greet if user exists

// Avoiding null/undefined errors
let data = null;
data && data.process();  // Doesn't execute (data is null)

2. Guarding Against Null/Undefined:

// Without short-circuit (can cause errors)
// let length = data.length;  // ❌ Error if data is null

// With short-circuit (safe)
let length = data && data.length;  // Returns null if data is null

// More examples
let user = null;
let userName = user && user.name;  // null (doesn't try to access .name)

let config = { timeout: 5000 };
let timeout = config && config.timeout;  // 5000

3. Setting Values Conditionally:

// Only set if condition is met
let isPremium = true;
let discountRate = isPremium && 0.2;  // 0.2 if premium, false otherwise

// Multiple conditions
let hasAccess = isLoggedIn && isPremium && isActive;

4. Inline Conditions:

// React-like pattern (conditional rendering)
let showWelcome = true;
let message = showWelcome && "Welcome to our site!";
console.log(message);  // "Welcome to our site!"

// Multiple checks
let canEdit = isOwner && !isLocked && hasPermission;

Logical OR (||)

The logical OR operator returns true if at least one operand is true. It returns false only if both are false.

Syntax

operand1 || operand2

Truth Table

operand1  operand2  result
───────────────────────────
true      true      true
true      false     true
false     true      true
false     false     false

Rule: At least one must be true for the result to be true.

Basic Examples

// At least one true
console.log(true || true);     // true
console.log(true || false);    // true
console.log(false || true);    // true

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

// With comparison operators
console.log(5 > 3 || 10 < 8);  // true (first is true)
console.log(5 < 3 || 10 > 8);  // true (second is true)
console.log(5 < 3 || 10 < 8);  // false (both false)

Real-World Examples

// Age discount (senior or child)
let age = 70;

if (age < 12 || age >= 65) {
  console.log("Eligible for discount");
}

// Weekend check
let day = "Saturday";

if (day === "Saturday" || day === "Sunday") {
  console.log("It's the weekend!");
}

// Emergency contact
let hasEmail = false;
let hasPhone = true;

if (hasEmail || hasPhone) {
  console.log("Can contact user");
} else {
  console.log("No contact method available");
}

// Access check (admin or owner)
let isAdmin = false;
let isOwner = true;

if (isAdmin || isOwner) {
  console.log("Access granted");
}

Short-Circuit Evaluation with OR (||)

The OR operator also uses short-circuit evaluation. If the first operand is truthy, it immediately returns that value without evaluating the second operand.

How Short-Circuit Works

// If first is true, second is never evaluated
true || console.log("This never runs");  // Doesn't log anything

// If first is false, second is evaluated
false || console.log("This runs");  // Logs "This runs"

Short-Circuit Returns the Value

Key Point: || returns the first truthy value or the last value if all are falsy.

// Returns first truthy value
console.log("hello" || "world");   // "hello" (first truthy)
console.log(5 || 10);              // 5 (first truthy)
console.log(true || false);        // true (first truthy)
console.log(1 || 0);               // 1 (first truthy)

// Returns last value if all falsy
console.log(0 || false);           // false (last value)
console.log(null || undefined);    // undefined (last value)
console.log("" || 0);              // 0 (last value)
console.log(false || null || 0);   // 0 (last value)

// Mixed values
console.log(0 || "default");       // "default" (first truthy)
console.log("" || "fallback");     // "fallback" (first truthy)
console.log(null || "value");      // "value" (first truthy)

Practical Uses of Short-Circuit OR

1. Default Values (Most Common Use):

// Provide default value if variable is falsy
let userName = "";
let displayName = userName || "Guest";
console.log(displayName);  // "Guest"

// Function parameters
function greet(name) {
  name = name || "there";
  console.log(`Hello, ${name}!`);
}

greet("John");  // "Hello, John!"
greet();        // "Hello, there!"

// Configuration defaults
let config = {
  timeout: null,
  retries: 0
};

let timeout = config.timeout || 5000;   // 5000
let retries = config.retries || 3;      // 3 (but 0 is falsy!)

⚠️ Problem with OR for Default Values:

// ❌ Problem: 0 and false are falsy
let count = 0;
let displayCount = count || 10;
console.log(displayCount);  // 10 (but we wanted 0!)

let isEnabled = false;
let enabled = isEnabled || true;
console.log(enabled);  // true (but we wanted false!)

// ✅ Solution: Use ?? (nullish coalescing) instead
let displayCount2 = count ?? 10;
console.log(displayCount2);  // 0 (correct!)

let enabled2 = isEnabled ?? true;
console.log(enabled2);  // false (correct!)

2. Fallback Chain:

// Try multiple sources
let value = localStorage.getItem("theme") || 
            userPreference || 
            "light";

// Try multiple API endpoints
let apiUrl = primaryApi || backupApi || defaultApi;

// Multiple fallback values
let name = user.nickname || user.username || user.email || "Anonymous";

3. Early Return:

function processData(data) {
  // Return early if no data
  return data || "No data provided";
}

console.log(processData("hello"));  // "hello"
console.log(processData(null));     // "No data provided"

4. Conditional Assignment:

// Set variable only if current is falsy
let message = "";
message = message || "Default message";
console.log(message);  // "Default message"

// With multiple conditions
let accessLevel = userLevel || groupLevel || defaultLevel;

Logical NOT (!)

The logical NOT operator inverts (negates) a boolean value. It converts true to false and false to true.

Syntax

!operand

Truth Table

operand   result
─────────────────
true      false
false     true

Basic Examples

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

// Double NOT (converts to boolean)
console.log(!!true);     // true
console.log(!!false);    // false

// With comparisons
console.log(!(5 > 3));   // false (inverts true)
console.log(!(5 < 3));   // true (inverts false)

NOT with Truthy/Falsy Values

The ! operator first converts the value to boolean, then inverts it:

// 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(!42);        // false
console.log(![]);        // false (arrays are truthy)
console.log(!{});        // false (objects are truthy)

Double NOT (!!) - Boolean Conversion

Using !! converts any value to its boolean equivalent:

// Convert to boolean
console.log(!!1);           // true
console.log(!!0);           // false
console.log(!!"hello");     // true
console.log(!!"");          // false
console.log(!!null);        // false
console.log(!!undefined);   // false
console.log(!![1, 2, 3]);   // true
console.log(!!{});          // true

// Equivalent to Boolean()
console.log(Boolean(1));    // true
console.log(!!1);           // true

Practical Examples

// Checking if NOT logged in
let isLoggedIn = false;

if (!isLoggedIn) {
  console.log("Please log in");
}

// Checking for empty values
let username = "";

if (!username) {
  console.log("Username is required");
}

// Inverting conditions
let hasError = true;

if (!hasError) {
  console.log("Process completed successfully");
} else {
  console.log("An error occurred");
}

// Toggle boolean
let isVisible = true;
isVisible = !isVisible;  // false
isVisible = !isVisible;  // true

// Checking for non-existence
let user = null;

if (!user) {
  console.log("No user found");
}

// Complex conditions
let age = 15;
let hasPermission = false;

if (!(age >= 18) || !hasPermission) {
  console.log("Access denied");
}

Negating Complex Expressions

// With parentheses
let a = 5;
let b = 10;

console.log(!(a > b));              // true (inverts false)
console.log(!(a < b && b > 0));     // false (inverts true)
console.log(!(a === 5 || b === 5)); // false (inverts true)

// Multiple NOTs
let isActive = true;
console.log(!isActive);     // false
console.log(!!isActive);    // true (back to original)
console.log(!!!isActive);   // false (negated again)

Combining Logical Operators

You can combine multiple logical operators to create complex conditions.

AND and OR Together

// (A && B) || C
let age = 25;
let hasLicense = true;
let isEmergency = false;

if ((age >= 18 && hasLicense) || isEmergency) {
  console.log("Can drive");
}

// A || (B && C)
let isWeekend = true;
let isHoliday = false;
let isVacation = false;

if (isWeekend || (isHoliday && isVacation)) {
  console.log("Day off");
}

Operator Precedence

Order of evaluation: !&&||

// NOT has highest precedence
console.log(!false && true);   // true
// Evaluated as: (!false) && true → true && true → true

// AND before OR
console.log(true || false && false);  // true
// Evaluated as: true || (false && false) → true || false → true

console.log(false && true || true);   // true
// Evaluated as: (false && true) || true → false || true → true

// Use parentheses for clarity!
console.log((true || false) && false);  // false
console.log(true || (false && false));  // true

Complex Real-World Examples

// Access control
function canAccessResource(user, resource) {
  return (user.isAdmin || user.isOwner) && 
         !resource.isDeleted && 
         (resource.isPublic || user.hasAccess);
}

// Form validation
function isValidRegistration(data) {
  return data.username && 
         data.username.length >= 3 &&
         data.email && 
         data.email.includes("@") &&
         data.password &&
         data.password.length >= 8 &&
         (data.age >= 18 || data.hasParentalConsent);
}

// Discount eligibility
function getDiscount(user, cart) {
  if (user.isPremium && cart.total > 100) {
    return 0.2;  // 20% off
  } else if (user.isPremium || cart.total > 200) {
    return 0.1;  // 10% off
  } else if (!user.isPremium && cart.total > 50 && cart.items >= 3) {
    return 0.05;  // 5% off
  }
  return 0;
}

// Availability check
function isAvailable(product) {
  return product.inStock && 
         !product.discontinued &&
         (product.quantity > 0 || product.preorderable) &&
         (!product.restricted || user.hasSpecialAccess);
}

Nullish Coalescing Operator (??)

The nullish coalescing operator returns the right operand when the left operand is null or undefined. Otherwise, it returns the left operand.

Syntax

operand1 ?? operand2

Introduced in ES2020

Why ?? Instead of ||?

The || operator treats all falsy values (0, false, "", etc.) the same. The ?? operator only checks for null and undefined.

// With || (checks for any falsy)
console.log(0 || 10);           // 10 (0 is falsy)
console.log(false || true);     // true (false is falsy)
console.log("" || "default");   // "default" ("" is falsy)

// With ?? (only checks null/undefined)
console.log(0 ?? 10);           // 0 (0 is NOT null/undefined)
console.log(false ?? true);     // false (false is NOT null/undefined)
console.log("" ?? "default");   // "" (empty string is NOT null/undefined)

// With null/undefined
console.log(null ?? 10);        // 10 (null)
console.log(undefined ?? 10);   // 10 (undefined)

Key Differences: || vs ??

// || treats these as "no value":
let count1 = 0 || 10;           // 10
let flag1 = false || true;      // true
let text1 = "" || "default";    // "default"

// ?? preserves these values:
let count2 = 0 ?? 10;           // 0 ✅
let flag2 = false ?? true;      // false ✅
let text2 = "" ?? "default";    // "" ✅

// Both treat null/undefined the same:
let val1 = null || 10;          // 10
let val2 = null ?? 10;          // 10
let val3 = undefined || 10;     // 10
let val4 = undefined ?? 10;     // 10

When to Use ??

Use ?? when 0, false, or "" are valid values that should be preserved.

// ❌ Bad - using || with 0
let quantity = 0;
let displayQty = quantity || 1;
console.log(displayQty);  // 1 (but we wanted 0!)

// ✅ Good - using ?? with 0
let quantity2 = 0;
let displayQty2 = quantity2 ?? 1;
console.log(displayQty2);  // 0 (correct!)

// ❌ Bad - using || with false
let notifications = false;
let showNotif = notifications || true;
console.log(showNotif);  // true (but we wanted false!)

// ✅ Good - using ?? with false
let notifications2 = false;
let showNotif2 = notifications2 ?? true;
console.log(showNotif2);  // false (correct!)

Practical Examples

// User settings with defaults
function getUserSetting(key, defaultValue) {
  let value = settings[key];
  return value ?? defaultValue;
}

let volume = getUserSetting("volume", 50);      // Preserves 0
let darkMode = getUserSetting("darkMode", true); // Preserves false

// API response with optional fields
function getUser(id) {
  let user = api.getUser(id);
  return {
    name: user.name ?? "Unknown",
    age: user.age ?? null,           // Preserves 0
    premium: user.premium ?? false,  // Preserves false
    email: user.email ?? ""
  };
}

// Configuration
let config = {
  port: 0,              // 0 is valid (use any available port)
  debug: false,         // false is valid
  apiKey: null,         // null means not set
  timeout: undefined    // undefined means not set
};

let port = config.port ?? 3000;           // 0 (preserved)
let debug = config.debug ?? true;         // false (preserved)
let apiKey = config.apiKey ?? "default";  // "default" (null replaced)
let timeout = config.timeout ?? 5000;     // 5000 (undefined replaced)

// Form inputs
let userInput = {
  age: 0,           // Valid: newborn
  name: "",         // Invalid: required
  subscribe: false  // Valid: user declined
};

let age = userInput.age ?? 18;            // 0 (preserved)
let name = userInput.name || "Anonymous"; // "Anonymous" (use || for required fields)
let subscribe = userInput.subscribe ?? true; // false (preserved)

Chaining ??

// Try multiple sources
let value = primary ?? secondary ?? tertiary ?? "default";

// Real example
let theme = 
  userPreference ?? 
  savedTheme ?? 
  systemTheme ?? 
  "light";

// Database fallback
let data = 
  cache ?? 
  database.get() ?? 
  defaultData;

Combining ?? with Other Operators

// ?? with &&
let user = getUser();
let userName = user && (user.name ?? "Anonymous");

// ?? with ||
let value = setting ?? (configValue || defaultValue);

// ?? with ternary
let display = value ?? (isSpecial ? "special" : "normal");

Operator Precedence

Understanding precedence helps you write correct complex conditions.

Precedence Order (High to Low)

1. ! (NOT)
2. && (AND)
3. || (OR)
4. ?? (Nullish Coalescing)

Examples

// ! before &&
console.log(!false && true);
// (!false) && true → true && true → true

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

// Use parentheses for clarity
console.log((true || false) && false);
// (true || false) && false → true && false → false

// Complex expression
console.log(!true && false || true);
// ((!true) && false) || true
// (false && false) || true
// false || true
// true

Best Practice: Use Parentheses

// ❌ Hard to read
if (!isAdmin && hasAccess || isOwner) { }

// ✅ Clear intent
if ((!isAdmin && hasAccess) || isOwner) { }

// ❌ Ambiguous
if (a || b && c || d) { }

// ✅ Explicit
if (a || (b && c) || d) { }

Truth Tables Summary

AND (&&) - Both must be true

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

OR (||) - At least one must be true

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

NOT (!) - Inverts the value

A      !A
───────────
true   false
false  true

XOR (Exclusive OR) - Exactly one must be true

JavaScript doesn't have a built-in XOR operator, but you can create it:

// XOR implementation
function xor(a, b) {
  return (a || b) && !(a && b);
}

console.log(xor(true, true));   // false
console.log(xor(true, false));  // true
console.log(xor(false, true));  // true
console.log(xor(false, false)); // false

// Or using !== for booleans
console.log(true !== true);     // false
console.log(true !== false);    // true

Common Patterns and Idioms

Pattern 1: Guard Clauses

function processUser(user) {
  // Early return if invalid
  if (!user) return;
  if (!user.isActive) return;
  if (!user.hasAccess) return;
  
  // Process user
  console.log(`Processing ${user.name}`);
}

// With logical operators
function validateInput(input) {
  return input && 
         typeof input === "string" && 
         input.length > 0;
}

Pattern 2: Default Parameters

// Old way with ||
function greet(name, greeting) {
  name = name || "Guest";
  greeting = greeting || "Hello";
  console.log(`${greeting}, ${name}!`);
}

// Modern way with ?? (better for 0, false, "")
function greet2(name, greeting) {
  name = name ?? "Guest";
  greeting = greeting ?? "Hello";
  console.log(`${greeting}, ${name}!`);
}

// Best way: default parameters
function greet3(name = "Guest", greeting = "Hello") {
  console.log(`${greeting}, ${name}!`);
}

Pattern 3: Conditional Execution

// Execute if condition is true
isLoggedIn && showDashboard();

// Execute if condition is false
!hasError || reportError();

// Multiple conditions
user && user.isActive && user.notify("Welcome!");

Pattern 4: Fallback Values

// Configuration with fallbacks
let config = userConfig || defaultConfig;

// Multiple fallback sources
let apiUrl = 
  process.env.API_URL || 
  config.apiUrl || 
  "https://api.default.com";

// Using ??
let timeout = 
  userTimeout ?? 
  configTimeout ?? 
  5000;

Pattern 5: Feature Detection

// Check if feature exists before using
window.localStorage && localStorage.setItem("key", "value");

// With fallback
let storage = window.localStorage || memoryStorage;

// Multiple checks
if (navigator.geolocation && navigator.geolocation.getCurrentPosition) {
  navigator.geolocation.getCurrentPosition(success, error);
}

Pattern 6: Short-Circuit Assignment

// Only assign if current value is falsy
message = message || "Default message";

// Only assign if current value is null/undefined
volume = volume ?? 50;

// Conditional increment
count && count++;

// Add to array if exists
items && items.push(newItem);

Common Mistakes and Pitfalls

Mistake 1: Confusing && and || Behavior

// ❌ Wrong expectation
let value = false && "hello";
console.log(value);  // false (not "hello")

let value2 = true || "hello";
console.log(value2); // true (not "hello")

// ✅ Understanding return values
console.log(0 && 5);      // 0 (first falsy)
console.log(5 && 0);      // 0 (first falsy)
console.log(5 && 10);     // 10 (last if all truthy)
console.log(0 || 5);      // 5 (first truthy)
console.log(5 || 10);     // 5 (first truthy)

Mistake 2: Using || for Default Values with 0 or false

// ❌ Problem
let count = 0;
let displayCount = count || 10;
console.log(displayCount);  // 10 (wanted 0!)

let enabled = false;
let isEnabled = enabled || true;
console.log(isEnabled);  // true (wanted false!)

// ✅ Solution
let count2 = 0;
let displayCount2 = count2 ?? 10;
console.log(displayCount2);  // 0 ✅

let enabled2 = false;
let isEnabled2 = enabled2 ?? true;
console.log(isEnabled2);  // false ✅

Mistake 3: Not Using Parentheses

// ❌ Ambiguous
if (!isAdmin && hasAccess || isOwner) {
  // What's the actual logic here?
}

// ✅ Clear
if ((!isAdmin && hasAccess) || isOwner) {
  // Clearly: (not admin AND has access) OR is owner
}

Mistake 4: Relying on Short-Circuit Without Understanding

// ❌ Doesn't work as expected
let result = value && value.toUpperCase();
// If value is 0, result is 0 (not uppercase)

// ✅ Better
let result = value ? value.toUpperCase() : "";

// ❌ Unexpected side effects
count > 0 && count--;  // Works, but confusing

// ✅ Clearer
if (count > 0) {
  count--;
}

Mistake 5: Multiple NOT Operators

// ❌ Hard to read
if (!!!isActive) { }

// ✅ Simplify
if (!isActive) { }

// ❌ Confusing double negative
if (!!value) { }

// ✅ Explicit boolean conversion
if (Boolean(value)) { }

Best Practices

1. Use Parentheses for Clarity

// ❌ Unclear
if (a && b || c && d) { }

// ✅ Clear
if ((a && b) || (c && d)) { }

2. Choose the Right Operator for Defaults

// ❌ Wrong - loses 0 and false
let count = input || 10;
let flag = setting || true;

// ✅ Right - preserves 0 and false
let count = input ?? 10;
let flag = setting ?? true;

3. Avoid Excessive Short-Circuit Tricks

// ❌ Clever but unclear
isValid && processData() || showError();

// ✅ Clear and explicit
if (isValid) {
  processData();
} else {
  showError();
}

4. Keep Conditions Simple

// ❌ Complex
if ((a || b) && (c || d) && !(e || f) || g) { }

// ✅ Break it down
let hasBasicAccess = a || b;
let hasFeature = c || d;
let notRestricted = !(e || f);
let isSpecial = g;

if ((hasBasicAccess && hasFeature && notRestricted) || isSpecial) { }

5. Use Meaningful Names

// ❌ Unclear
if (a && b || c) { }

// ✅ Self-documenting
let canAccess = isLoggedIn && hasPermission;
let isSpecialCase = isAdmin;

if (canAccess || isSpecialCase) { }

6. Comment Complex Logic

// ✅ Good - explain complex conditions
function canPublish(user, post) {
  // User must be author OR admin
  // AND post must not be deleted
  // AND either public OR user has access
  return (user.isAuthor(post) || user.isAdmin) &&
         !post.isDeleted &&
         (post.isPublic || user.hasAccess(post));
}

Practical Examples

Example 1: User Authentication

function authenticateUser(username, password, rememberMe) {
  // Validate inputs
  if (!username || !password) {
    return { success: false, message: "Username and password required" };
  }
  
  if (username.length < 3 || password.length < 8) {
    return { success: false, message: "Invalid credentials format" };
  }
  
  // Check credentials
  let user = database.findUser(username);
  
  if (!user || !user.checkPassword(password)) {
    return { success: false, message: "Invalid credentials" };
  }
  
  // Check if account is active
  if (!user.isActive || user.isBanned) {
    return { success: false, message: "Account is inactive" };
  }
  
  // Create session
  let sessionDuration = rememberMe ? 30 * 24 * 60 * 60 : 24 * 60 * 60;
  createSession(user, sessionDuration);
  
  return { success: true, user: user };
}

Example 2: Form Validation

function validateForm(formData) {
  let errors = [];
  
  // Name validation
  if (!formData.name || formData.name.trim() === "") {
    errors.push("Name is required");
  } else if (formData.name.length < 2 || formData.name.length > 50) {
    errors.push("Name must be between 2 and 50 characters");
  }
  
  // Email validation
  if (!formData.email || formData.email.trim() === "") {
    errors.push("Email is required");
  } else if (!formData.email.includes("@") || !formData.email.includes(".")) {
    errors.push("Invalid email format");
  }
  
  // Age validation
  if (formData.age == null) {
    errors.push("Age is required");
  } else if (formData.age < 18 && !formData.hasParentalConsent) {
    errors.push("Must be 18+ or have parental consent");
  }
  
  // Terms validation
  if (!formData.agreedToTerms) {
    errors.push("Must agree to terms and conditions");
  }
  
  return {
    isValid: errors.length === 0,
    errors: errors
  };
}

Example 3: Access Control System

const PERMISSIONS = {
  READ: 1,
  WRITE: 2,
  DELETE: 4,
  ADMIN: 8
};

function hasPermission(user, requiredPermission) {
  // Admins have all permissions
  if (user.role === "admin") {
    return true;
  }
  
  // Check specific permission
  return (user.permissions & requiredPermission) !== 0;
}

function canAccessResource(user, resource) {
  // Resource must exist and not be deleted
  if (!resource || resource.isDeleted) {
    return false;
  }
  
  // Public resources are accessible to everyone
  if (resource.isPublic) {
    return true;
  }
  
  // User must be logged in for private resources
  if (!user) {
    return false;
  }
  
  // Owner always has access
  if (user.id === resource.ownerId) {
    return true;
  }
  
  // Check if user has explicit access
  if (resource.sharedWith && resource.sharedWith.includes(user.id)) {
    return true;
  }
  
  // Check role-based access
  if (user.role === "admin" || user.role === "moderator") {
    return true;
  }
  
  return false;
}

Example 4: Feature Flags

function isFeatureEnabled(featureName, user) {
  let feature = features[featureName];
  
  // Feature doesn't exist
  if (!feature) {
    return false;
  }
  
  // Feature is globally disabled
  if (!feature.enabled) {
    return false;
  }
  
  // Feature is in beta
  if (feature.beta) {
    // Only beta users or admins
    return user && (user.isBetaTester || user.isAdmin);
  }
  
  // Feature has percentage rollout
  if (feature.rolloutPercentage != null) {
    return Math.random() * 100 < feature.rolloutPercentage;
  }
  
  // Feature has user whitelist
  if (feature.whitelist) {
    return user && feature.whitelist.includes(user.id);
  }
  
  // Feature has minimum tier requirement
  if (feature.minimumTier) {
    return user && user.tier >= feature.minimumTier;
  }
  
  // Feature is fully enabled
  return true;
}

Practice Exercises

Create a file called logical-practice.js:

// Logical Operators Practice

// Exercise 1: Age Verification
function canBuyAlcohol(age, hasID) {
  // TODO: Return true if age >= 21 AND has ID
  // Handle null/undefined age
}

// Test
console.log(canBuyAlcohol(25, true));   // true
console.log(canBuyAlcohol(25, false));  // false
console.log(canBuyAlcohol(18, true));   // false
console.log(canBuyAlcohol(null, true)); // false

// Exercise 2: Discount Calculator
function getDiscountRate(isMember, orderTotal, itemCount) {
  // TODO: Return discount rate
  // Premium member (20%): isMember AND orderTotal > 100
  // Bulk discount (15%): itemCount > 10
  // Member discount (10%): isMember
  // High value (5%): orderTotal > 200
  // Otherwise: 0
}

// Test
console.log(getDiscountRate(true, 150, 5));   // 0.2
console.log(getDiscountRate(false, 50, 15));  // 0.15
console.log(getDiscountRate(true, 50, 5));    // 0.1

// Exercise 3: Password Validator
function isPasswordStrong(password) {
  // TODO: Return true if ALL conditions met:
  // - Length >= 8
  // - Has uppercase letter
  // - Has lowercase letter
  // - Has number
  // - Has special character (!@#$%^&*)
}

// Test
console.log(isPasswordStrong("MyP@ss1"));     // false (too short)
console.log(isPasswordStrong("MyP@ssw0rd"));  // true

// Exercise 4: Safe Navigation
function getUserCity(user) {
  // TODO: Return user's city using short-circuit
  // Return "Unknown" if any part is null/undefined
  // Path: user.profile.address.city
}

// Test
console.log(getUserCity({ profile: { address: { city: "NYC" } } }));  // "NYC"
console.log(getUserCity({ profile: null }));  // "Unknown"
console.log(getUserCity(null));  // "Unknown"

// Exercise 5: Default Configuration
function initConfig(userConfig) {
  // TODO: Merge with defaults using ?? (not ||!)
  // Defaults: { port: 3000, debug: false, timeout: 5000, retries: 3 }
  // Preserve 0 and false from userConfig
}

// Test
console.log(initConfig({ port: 0, debug: true }));
// Should preserve port: 0, debug: true, add timeout: 5000, retries: 3

// Exercise 6: Complex Condition
function canEditPost(user, post) {
  // TODO: Return true if ANY of these:
  // 1. User is admin
  // 2. User is author AND post is not locked
  // 3. User is moderator AND post age < 24 hours
}

// Test various scenarios

Summary

Congratulations! 🎉 You now have a comprehensive understanding of logical operators in JavaScript.

Key Takeaways

Logical AND (&&)

  • Both operands must be truthy
  • Returns first falsy value OR last value
  • Short-circuit: stops at first falsy
  • Use for combining conditions

Logical OR (||)

  • At least one operand must be truthy
  • Returns first truthy value OR last value
  • Short-circuit: stops at first truthy
  • Use for default values (watch out for 0, false, "")

Logical NOT (!)

  • Inverts boolean value
  • Converts to boolean first
  • !! converts any value to boolean
  • Use for negating conditions

Nullish Coalescing (??)

  • Only checks for null/undefined
  • Preserves 0, false, ""
  • Use when these are valid values
  • Better than || for default values

Operator Precedence:

  • ! (highest)
  • &&
  • ||
  • ?? (lowest)

Best Practices:

  • Use parentheses for clarity
  • Choose ?? over || when appropriate
  • Keep conditions simple and readable
  • Avoid clever short-circuit tricks
  • Comment complex logic

Common Patterns:

  • Guard clauses
  • Default values
  • Conditional execution
  • Feature detection
  • Fallback chains

What's Next?

Excellent work! You now understand how to combine and manipulate boolean logic.

You've completed the core operators! Next, you can explore:

  • Conditional Statements (if/else, switch)
  • Ternary Operator (in-depth)
  • Loops (for, while, do-while)
  • Functions

Understanding logical operators is crucial for all of these topics!