Debugging Microservices & Distributed Systems
7 min read

JavaScript Operators: '||' vs '&&' vs '??'

Master JavaScript logical operators with practical examples and best practices

JavaScript’s logical operators (||, &&, ??) are powerful tools that go way beyond simple true/false operations. Let’s dive into how they work, explore some clever use cases, and see how they can make your code cleaner and more elegant.

The OR Operator ||: Default Values Made Simple

The OR operator (||) is a shortcut for setting default values. It returns the first “truthy” value it finds, making it perfect for fallback values. If you’re new to truthy and falsy values, here’s a helpful guide.

JavaScript
// Basic boolean operations
true || false // returns true
false || true // returns true
false || false // returns false
// Working with different types
"hello" || "world" // returns "hello"
"" || "fallback" // returns "fallback"
null || "default" // returns "default"
undefined || 42 // returns 42

This pattern shines when setting default values:

JavaScript
// Clean default value syntax
function greet(name) {
return `Hello, ${name || 'friend'}!`;
}
// Useful for configuration objects
const config = {
port: process.env.PORT || 3000,
host: process.env.HOST || 'localhost',
timeout: process.env.TIMEOUT || 5000
};

The AND Operator &&: Elegant Conditional Execution

The AND operator (&&) is your friend for conditional execution. It returns the first “falsy” value or the last value if everything is truthy. This creates a clean way to run code conditionally.

JavaScript
// Boolean operations
true && false // returns false
true && true // returns true
false && true // returns false
// Working with values
"hello" && "world" // returns "world"
"" && "test" // returns ""
null && "anything" // returns null

The && operator in JavaScript does something subtle - it returns the last value if all conditions are true, or the first falsy value it encounters. This behavior enables two powerful patterns:

JavaScript
// Pattern 1: Single-line conditional execution
isValid && sendToServer(data);
// Under the hood, this is doing:
if (isValid) {
sendToServer(data);
}

This works because:

  • If isValid is false, && returns false and stops there
  • If isValid is true, && evaluates and returns the result of sendToServer(data)
JSX
// Pattern 2: Conditional rendering in React
return (
<div>
{isLoggedIn && <UserDashboard />}
{hasError && <ErrorMessage text={errorText} />}
</div>
);
// React processes this as:
{true && <Component />} // renders <Component />
{false && <Component />} // renders false (React ignores it)

The magic happens because:

  • React ignores false, null, and undefined in JSX
  • When isLoggedIn is true, && returns the component
  • When isLoggedIn is false, && returns false (which React ignores)

Watch Out For These Cases

JSX
// Potential gotcha with numbers
const count = 0;
return (
<div>
{count && <DisplayCount number={count} />} // Oops! Renders 0
</div>
);
// Safer version
return (
<div>
{count !== undefined && <DisplayCount number={count} />}
{/* or */}
{count >= 0 && <DisplayCount number={count} />}
</div>
);

JavaScript Operators: '||' vs '&&' vs '??'

  • Any falsy value (0, '', null, undefined, false) will short-circuit &&
  • In React, only false, null, and undefined are truly “invisible”
  • Other falsy values like 0 or '' will actually render

The Nullish Coalescing Operator ??: Smart Defaults

Think of ?? as a smarter way to set default values. Unlike || which triggers on any falsy value, ?? only triggers on null or undefined. Here’s why this difference matters:

JavaScript
// Let's see what || considers "falsy":
0 || "default" // returns "default"
"" || "default" // returns "default"
false || "default" // returns "default"
null || "default" // returns "default"
undefined || "default" // returns "default"
// Now compare with ??:
0 ?? "default" // returns 0 ✨
"" ?? "default" // returns "" ✨
false ?? "default" // returns false ✨
null ?? "default" // returns "default"
undefined ?? "default" // returns "default"

This becomes super useful when working with numbers or strings where 0 or "" are meaningful values:

JavaScript
// Working with quantities
function updateQuantity(newQuantity) {
// BAD: || turns 0 into 1
cart.quantity = newQuantity || 1; // 0 becomes 1
// GOOD: ?? keeps 0 as 0
cart.quantity = newQuantity ?? 1; // 0 stays 0
}
// Working with form inputs
function updateProfile(formData) {
// BAD: || replaces empty string with default
user.bio = formData.bio || "No bio yet"; // "" becomes "No bio yet"
// GOOD: ?? keeps empty string
user.bio = formData.bio ?? "No bio yet"; // "" stays as ""
}
// Real-world example: API response handling
function processUserData(response) {
return {
name: response.name ?? 'Anonymous',
posts: response.posts ?? [],
score: response.score ?? 0, // 0 is a valid score
bio: response.bio ?? '', // Empty bio is valid
lastLogin: response.lastLogin ?? null
};
}

The ?? operator really shines when:

  1. Working with numbers where 0 is valid
  2. Handling strings where empty string is meaningful
  3. Processing API responses where you need to distinguish between “not set” (null/undefined) and “intentionally empty” (0/"")

You can also chain it for multiple fallbacks:

JavaScript
// Multiple fallbacks while preserving 0 and ""
const username = input ?? stored ?? generated ?? 'guest';
// Useful in configuration
const config = {
timeout: process.env.TIMEOUT ?? defaultTimeout ?? 5000,
retries: process.env.RETRIES ?? settings.retries ?? 3,
path: process.env.PATH ?? defaultPath ?? ''
};

Choosing the Right Operator

Let’s look at common scenarios and which operator fits best:

  1. Need a default value? Here’s how to choose:
JavaScript
// When 0 or empty string are NOT valid values, use ||
function getName(user) {
return user.name || 'Anonymous'; // empty string becomes 'Anonymous'
// This handles all these cases:
'' || 'Anonymous' // returns 'Anonymous'
0 || 'Anonymous' // returns 'Anonymous'
null || 'Anonymous' // returns 'Anonymous'
false || 'Anonymous' // returns 'Anonymous'
}
// When 0 or empty string ARE valid values, use ??
function getQuantity(product) {
return product.quantity ?? 1; // 0 stays 0, only null/undefined become 1
// This handles the edge cases better:
0 ?? 1 // returns 0 (preserves valid zero)
'' ?? 'default' // returns '' (preserves empty string)
null ?? 1 // returns 1
undefined ?? 1 // returns 1
}
  1. Need conditional execution? Use &&
JavaScript
// The && operator is perfect when you want to:
// 1. Run code only if a condition is true
// 2. Show UI elements conditionally
// 3. Access nested properties safely
// Function calls
function processUser(user) {
// Only calls expensive function if user is premium
user.isPremium && loadPremiumFeatures(user);
// Safer than if statements for nullable objects
user?.settings?.theme && applyTheme(user.settings.theme);
}
// Error handling
function submitForm(data) {
// Multiple conditions
isValid && !isLoading && sendToServer(data);
// With error handling
isValid && handleSuccess() || handleError();
}
// React conditional rendering
function UserProfile({ user, isAdmin }) {
return (
<div>
{/* Basic conditional render */}
{isLoggedIn && <UserDashboard />}
{/* Multiple conditions */}
{isAdmin && user.permissions && <AdminControls />}
{/* Conditional with nested properties */}
{user?.subscription?.isActive && <PremiumContent />}
{/* Combining conditions */}
{(isAdmin || user.isModerator) && <ModTools />}
</div>
);
}
  1. Need multiple fallbacks? Combine them with parentheses:
JavaScript
// Multiple fallbacks with clear precedence
const theme = (userTheme ?? systemTheme) ?? 'light';
const name = (user?.name ?? savedName) ?? 'Anonymous';
// Real-world example: Complex configuration
const config = {
// Environment variables with fallbacks
port: (process.env.PORT ?? customPort) ?? 3000,
// User preferences with system defaults
theme: (userPrefs?.theme ?? systemTheme) ?? 'light',
// Feature flags with multiple layers
features: {
dark: (userFlags?.dark ?? betaFlags?.dark) ?? false,
experimental: (user?.beta && betaFeatures) ?? false
},
// API configuration with timeouts
api: {
timeout: (customTimeout ?? defaultTimeout) ?? 5000,
retries: (userRetries ?? systemRetries) ?? 3,
// Combining different operators for complex logic
endpoint: (isProd && PROD_API) || (isStaging && STAGING_API) || DEV_API
}
};

These operators are more than just syntax shortcuts - they’re tools for writing cleaner, more expressive code. Next time you’re about to write an if statement, consider if one of these operators might make your code more elegant.

My suggestion is to start with the nullish coalescing operator ?? for default values. It’s the newest addition to JavaScript, and it often leads to fewer surprises than the older OR operator ||


Related Articles

If you enjoyed this article, you might find these related pieces interesting as well.

Recommended Engineering Resources

Here are engineering resources I've personally vetted and use. They focus on skills you'll actually need to build and scale real projects - the kind of experience that gets you hired or promoted.

Imagine where you would be in two years if you actually took the time to learn every day. A little effort consistently adds up, shaping your skills, opening doors, and building the career you envision. Start now, and future you will thank you.


This article was originally published on https://www.trevorlasn.com/blog/javascript-logical-operators. It was written by a human and polished using grammar tools for clarity.

Interested in a partnership? Shoot me an email at hi [at] trevorlasn.com with all relevant information.