βοΈ Comparison and Logical Operators
Every "if" your program has ever run started with a comparison. This lesson gives you the operators that ask questions β is this equal to that? is either true? β and the crucial habit of using === so JavaScript never surprises you with a silent type conversion.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain the difference between strict (
===) and loose (==) equality and default to strict - Predict the result of relational and string comparisons, including lexicographic ordering
- Describe JavaScript's truthy/falsy values and use them safely
- Use short-circuit evaluation with
&&and||for guards and defaults - Choose between
||and the nullish coalescing operator??, and write clean ternary expressions
Estimated Time: 30β40 minutes β’ Difficulty: Beginner
Hands-on: Build a password-strength checker driven entirely by comparison and logical operators.
In This Lesson
Operators That Ask Questions
Arithmetic operators produce values. Comparison and logical operators evaluate them β they answer yes/no questions and return a Boolean (true or false). Together they are the engine behind every if, every loop condition, and every filter.
π‘ Analogy β the doorman. A club doorman checks IDs (comparison) and enforces several rules at once: "over 21 and on the guest list, or a member" (logical). Comparison and logical operators are how your code plays doorman over data.
Equality: === vs. ==
JavaScript has two equality operators, and the difference between them is one of the most important things a beginner learns.
| Operator | Name | Compares | Example |
|---|---|---|---|
=== | Strict equality | Value and type β no conversion | 5 === "5" β false |
== | Loose equality | Value only β converts types first | 5 == "5" β true |
!== | Strict inequality | Value or type differs | 5 !== "5" β true |
!= | Loose inequality | Value differs after conversion | 5 != "5" β false |
// Loose (==) converts before comparing β the source of many bugs
console.log(5 == '5'); // true (string β number)
console.log(0 == false); // true (false β 0)
console.log('' == 0); // true ('' β 0)
console.log(null == undefined); // true (special case)
// Strict (===) never converts β what you see is what you get
console.log(5 === '5'); // false (number vs string)
console.log(0 === false); // false (number vs boolean)
console.log(null === undefined); // false (different types)
π‘ Analogy β two passport checks. Loose equality is a guard who only confirms you're the right person and ignores which kind of passport you carry. Strict equality checks the person and the passport type. The stricter check is the safer one.
β
Rule of thumb: always use ===
Professional JavaScript defaults to === and !==. Loose == triggers a set of conversion rules that are hard to remember and easy to get wrong. The one common, deliberate use of loose equality is x == null, which conveniently matches both null and undefined β but even that is a stylistic choice.
Relational & String Comparison
The relational operators >, <, >=, and <= compare magnitude and return a Boolean. With numbers they behave exactly as you'd expect; with strings they compare lexicographically β character by character using Unicode code points.
// Numeric comparison
console.log(10 > 5); // true
console.log(5 >= 5); // true
console.log('10' > 5); // true β the string '10' is coerced to number 10
// String comparison is dictionary-style, NOT numeric
console.log('apple' < 'banana'); // true ('a' < 'b')
console.log('Apple' < 'apple'); // true (uppercase code points come first)
console.log('10' < '9'); // true ('1' < '9', so '10' sorts before '9'!)
β οΈ The classic sorting bug
'10' < '9' is true because comparison stops at the first character. This is why [1, 2, 10].sort() returns [1, 10, 2] β sort() compares stringified values by default. For numbers, always pass a comparator: arr.sort((a, b) => a - b).
For human-facing sorting of text β names, cities, anything with accents β use localeCompare(), which respects language rules:
const names = ['ZΓΌrich', 'Γpfel', 'Andres'];
names.sort((a, b) => a.localeCompare(b, 'de'));
console.log(names); // correct German ordering
// A tidy case-insensitive equality helper
const equalsIgnoreCase = (a, b) =>
a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;
console.log(equalsIgnoreCase('Hello', 'hello')); // true
π Note β objects compare by reference
{a: 1} === {a: 1} is false. Objects and arrays are compared by identity (are they the same object in memory?), not by contents. Two separately-created objects are never ===, even with identical data. To compare contents you must walk their properties or use a library helper.
Truthy, Falsy & Booleans
Logical operators don't require actual Booleans β they work on the truthiness of any value. It is quicker to memorise the short list of falsy values; everything else is truthy.
β οΈ The eight falsy values
false, 0, -0, 0n (BigInt zero), "" (empty string), null, undefined, and NaN. That's it. Everything else β including "0", "false", [], and {} β is truthy.
// The double-NOT idiom converts any value to a real boolean
console.log(!!'hello'); // true
console.log(!!0); // false
console.log(!![]); // true β an empty array is truthy!
console.log(!!''); // false
This matters because the surprising ones β [] is truthy, "0" is truthy β cause real bugs when used as conditions. When you specifically mean "is this array empty?", test arr.length === 0, not !arr.
Logical Operators & Short-Circuiting
The three core logical operators combine or invert conditions:
| Operator | Name | Returns |
|---|---|---|
&& | AND | Truthy only if both sides are truthy |
|| | OR | Truthy if either side is truthy |
! | NOT | Inverts a value to the opposite Boolean |
π‘ Analogy β circuits.&&is a series circuit: current flows only if every switch is on.||is a parallel circuit: current flows if any switch is on.!is an inverter that flips on to off.
Short-circuit evaluation
JavaScript evaluates left to right and stops as soon as the answer is certain. && stops at the first falsy operand; || stops at the first truthy one. Crucially, they return the operand itself, not a coerced Boolean β which enables two everyday patterns:
// Guard: only read .name if user exists (avoids a TypeError)
const user = null;
console.log(user && user.name); // null β the right side is never touched
// Default: fall back when the left side is falsy
const input = '';
const label = input || 'Untitled';
console.log(label); // 'Untitled'
// Returning the operand, not just true/false
console.log('Hello' && 'World'); // 'World' (last truthy value)
console.log('' || 'Fallback'); // 'Fallback' (first truthy value)
π Worked example β a permission check
Real access rules combine several conditions. Grouping with parentheses makes intent unmistakable:
function canEnter({ age, hasTicket, isMember }) {
// Members always get in; everyone else needs to be 18+ with a ticket.
return isMember || (age >= 18 && hasTicket);
}
console.log(canEnter({ age: 25, hasTicket: true, isMember: false })); // true
console.log(canEnter({ age: 16, hasTicket: true, isMember: false })); // false
console.log(canEnter({ age: 16, hasTicket: false, isMember: true })); // true
Nullish Coalescing (??)
Using || for default values has a subtle flaw: it replaces every falsy value, including legitimate ones like 0, false, and "". The nullish coalescing operator ?? fixes this by falling back only when the left side is null or undefined.
// || treats 0 and '' as "missing" β often wrong
console.log(0 || 'default'); // 'default' β 0 was a valid value!
console.log('' || 'default'); // 'default'
// ?? only falls back for null / undefined
console.log(0 ?? 'default'); // 0 β preserved
console.log('' ?? 'default'); // '' β preserved
console.log(null ?? 'default'); // 'default'
console.log(undefined ?? 'x'); // 'x'
π‘ Analogy β the backup generator.??is a generator that only kicks in when the main power is completely out (null/undefined). A mere flicker β a0or an empty string β is still real power, so the generator stays off.
?? pairs naturally with optional chaining (?.), which safely reads deep properties that might not exist:
const user = { name: 'Alice', address: { city: 'New York' } };
// Old, verbose safe access
let country;
if (user && user.address && user.address.country) {
country = user.address.country;
} else {
country = 'Unknown';
}
// Modern one-liner
const country2 = user?.address?.country ?? 'Unknown';
console.log(country2); // 'Unknown'
β οΈ Don't mix ?? with &&/|| unparenthesised
JavaScript makes a ?? b || c a syntax error on purpose, to force you to be explicit. Write (a ?? b) || c or a ?? (b || c) so the intent is unambiguous.
The Ternary Operator
The conditional (ternary) operator is the only JavaScript operator that takes three operands: condition ? valueIfTrue : valueIfFalse. It is an expression, so unlike an if statement it produces a value you can assign or return.
const age = 20;
// Instead of a four-line if/else...
const status = age >= 18 ? 'Adult' : 'Minor';
console.log(status); // 'Adult'
// Chained ternaries read like a lookup table (keep them shallow)
function greetingFor(hour) {
return hour < 12 ? 'Good morning'
: hour < 18 ? 'Good afternoon'
: 'Good evening';
}
console.log(greetingFor(9)); // 'Good morning'
console.log(greetingFor(20)); // 'Good evening'
π‘ Analogy β a signposted fork. The ternary is a fork in the road with one clear sign: true goes left, false goes right. Both paths rejoin at the next line β you just pick up a different value along the way.
β οΈ When to stop
Ternaries are perfect for choosing between two values. Once you're nesting three or more, or running side effects in each branch, a plain if or a switch reads far better. Prefer clarity over cleverness.
Hands-on Exercise
ποΈ Build a password-strength checker
Objective: Write checkPassword(pw) that returns an object of { valid, errors, strength } using comparison and logical operators.
Rules:
- Reject a missing or non-string password up front.
- Collect an
errorsarray: at least 8 characters, at least one uppercase, one lowercase, one digit, and one symbol. validistrueonly whenerrorsis empty.- If valid, rate
strengthas"medium","strong", or"very strong"based on bonus factors (length β₯ 12, multiple digits, multiple symbols).
π‘ Hint
Use /[A-Z]/.test(pw) style regex tests for each character class. Count how many bonus conditions are met with [cond1, cond2, cond3].filter(Boolean).length, then map that count to a label with a ternary.
β Sample solution
function checkPassword(pw) {
const result = { valid: false, errors: [], strength: 'weak' };
if (typeof pw !== 'string' || pw.length === 0) {
result.errors.push('Password is required');
return result;
}
if (pw.length < 8) result.errors.push('At least 8 characters');
if (!/[A-Z]/.test(pw)) result.errors.push('One uppercase letter');
if (!/[a-z]/.test(pw)) result.errors.push('One lowercase letter');
if (!/\d/.test(pw)) result.errors.push('One number');
if (!/[^A-Za-z0-9]/.test(pw)) result.errors.push('One symbol');
result.valid = result.errors.length === 0;
if (result.valid) {
const bonuses = [
pw.length >= 12,
(pw.match(/\d/g) || []).length >= 3,
(pw.match(/[^A-Za-z0-9]/g) || []).length >= 2
].filter(Boolean).length;
result.strength = bonuses === 0 ? 'medium'
: bonuses === 1 ? 'strong'
: 'very strong';
}
return result;
}
console.log(checkPassword('abc')); // valid:false, several errors
console.log(checkPassword('Passw0rd!')); // valid:true, strength:'medium'
console.log(checkPassword('Sup3r$ecret99')); // valid:true, strength:'very strong'
π― Quick Quiz
Question 1: What does 0 === false evaluate to?
Question 2: Which operator preserves 0 and "" as valid values when supplying a default?
Question 3: Why does '10' < '9' return true?
Summary & Quiz
π Key Takeaways
- Default to strict
===/!==; loose==converts types and hides bugs. - Strings compare lexicographically β use a numeric comparator for numbers and
localeComparefor human text. - Only eight values are falsy;
[]and"0"are truthy. &&and||short-circuit and return an operand, powering guards and defaults.- Use
??(not||) when0/""/falseare valid, and keep ternaries shallow.
π Further Reading
- MDN β Expressions and Operators
- MDN β Nullish Coalescing Operator
- MDN β Truthy & Falsy
- JavaScript.info β Logical Operators
π What's Next?
You've now met arithmetic, comparison, and logical operators. In Operator Precedence and Expressions you'll learn the rules that decide which operator runs first when you combine them β and how to keep complex expressions readable.
π Great progress!
Your code can now make decisions. Next, we untangle how those decisions are ordered.