π§ Lesson 3.3: Logical Operators and Truthiness
Real decisions are rarely simple yes/no questions. Logical operators let you combine conditions β and truthiness determines what each language considers "true enough."
π― Learning Objectives
By the end of this lesson, you will be able to:
- Use AND, OR, and NOT operators in all three languages
- Understand Python's word-based operators (
and,or,not) vs. JS/C#'s symbol-based operators (&&,||,!) - Explain short-circuit evaluation and why it matters
- Identify truthy and falsy values in Python and JavaScript
- Understand why C# takes a stricter approach to boolean logic
- Combine logical operators with comparison operators to build complex conditions
Estimated Time: 45 minutes
Project: An access-control checker that validates multiple criteria
π In This Lesson
The Three Logical Operators
Logical operators combine boolean values (or expressions that produce booleans) to make more complex decisions. There are exactly three:
| Logic | Meaning | True when⦠|
|---|---|---|
| AND | Both must be true | True AND True β True (all other combos β False) |
| OR | At least one must be true | False OR False β False (all other combos β True) |
| NOT | Flip the value | NOT True β False, NOT False β True |
Words vs. Symbols
This is the biggest syntax divergence in this lesson: Python uses English words, while JavaScript and C# use symbols.
| Operation | π Python | β‘ JavaScript | π· C# |
|---|---|---|---|
| AND | and |
&& |
&& |
| OR | or |
|| |
|| |
| NOT | not |
! |
! |
# Python uses English words: and, or, not
age = 25
has_id = True
# AND β both must be true
if age >= 18 and has_id:
print("Entry allowed")
# OR β at least one must be true
is_student = False
is_senior = True
if is_student or is_senior:
print("Discount available!")
# NOT β flips the value
is_banned = False
if not is_banned:
print("Welcome!")
// JavaScript uses symbols: &&, ||, !
let age = 25;
let hasId = true;
// AND β both must be true
if (age >= 18 && hasId) {
console.log("Entry allowed");
}
// OR β at least one must be true
let isStudent = false;
let isSenior = true;
if (isStudent || isSenior) {
console.log("Discount available!");
}
// NOT β flips the value
let isBanned = false;
if (!isBanned) {
console.log("Welcome!");
}
// C# uses the same symbols as JavaScript: &&, ||, !
int age = 25;
bool hasId = true;
// AND β both must be true
if (age >= 18 && hasId)
{
Console.WriteLine("Entry allowed");
}
// OR β at least one must be true
bool isStudent = false;
bool isSenior = true;
if (isStudent || isSenior)
{
Console.WriteLine("Discount available!");
}
// NOT β flips the value
bool isBanned = false;
if (!isBanned)
{
Console.WriteLine("Welcome!");
}
β οΈ Don't Mix Them Up!
Writing && in Python is a bitwise operator (very different behavior). Writing and in JavaScript or C# is a syntax error. The correct mapping is: Python and = JS/C# &&, Python or = JS/C# ||, Python not = JS/C# !.
Combining Conditions
The real power of logical operators is combining multiple comparisons into a single, expressive condition.
# Can this person rent a car?
age = 22
has_license = True
has_insurance = True
can_rent = age >= 21 and has_license and has_insurance
print(f"Can rent: {can_rent}") # True
# Is this a weekend or holiday?
day = "Saturday"
is_holiday = False
day_off = day in ("Saturday", "Sunday") or is_holiday
print(f"Day off: {day_off}") # True
# Is this a valid password?
password = "Secret123"
is_valid = (
len(password) >= 8
and any(c.isupper() for c in password)
and any(c.isdigit() for c in password)
)
print(f"Valid password: {is_valid}") # True
// Can this person rent a car?
let age = 22;
let hasLicense = true;
let hasInsurance = true;
let canRent = age >= 21 && hasLicense && hasInsurance;
console.log(`Can rent: ${canRent}`); // true
// Is this a weekend or holiday?
let day = "Saturday";
let isHoliday = false;
let dayOff = day === "Saturday" || day === "Sunday" || isHoliday;
console.log(`Day off: ${dayOff}`); // true
// Is this a valid password?
let password = "Secret123";
let isValid =
password.length >= 8 &&
/[A-Z]/.test(password) &&
/[0-9]/.test(password);
console.log(`Valid password: ${isValid}`); // true
// Can this person rent a car?
int age = 22;
bool hasLicense = true;
bool hasInsurance = true;
bool canRent = age >= 21 && hasLicense && hasInsurance;
Console.WriteLine($"Can rent: {canRent}"); // True
// Is this a weekend or holiday?
string day = "Saturday";
bool isHoliday = false;
bool dayOff = day == "Saturday" || day == "Sunday" || isHoliday;
Console.WriteLine($"Day off: {dayOff}"); // True
// Is this a valid password?
string password = "Secret123";
bool isValid =
password.Length >= 8 &&
password.Any(char.IsUpper) &&
password.Any(char.IsDigit);
Console.WriteLine($"Valid password: {isValid}"); // True
π‘ Readability Tip: Use Parentheses
When combining AND and OR in the same expression, add parentheses to make your intent crystal clear β even when they're not strictly required. Compare these two:
// Ambiguous (what gets evaluated first?)
if (a || b && c) ...
// Clear (AND is evaluated before OR anyway, but now it's obvious)
if (a || (b && c)) ...
π Instructor Note: Delivery Guidance
The car rental and password examples are great for showing how logical operators solve real problems. For the password validation, point out how each language takes a different approach: Python uses generator expressions, JavaScript uses regex, and C# uses LINQ. Don't deep-dive those features now β just note that the logical structure (condition1 AND condition2 AND condition3) is the same everywhere. The validation details are for later lessons.
Short-Circuit Evaluation
All three languages use short-circuit evaluation: they stop checking conditions as soon as the outcome is certain.
(B never checked!)"] B -->|Yes| D{"Is B true?"} D -->|No| E["Result: False"] D -->|Yes| F["Result: True"] style C fill:#f59e0b,stroke:#d97706,color:#fff,stroke-width:2px
- AND: If the first condition is false, the whole thing is false β skip the rest
- OR: If the first condition is true, the whole thing is true β skip the rest
# AND short-circuits on the first False
x = 0
# The second condition is never checked (would cause ZeroDivisionError)
if x != 0 and 10 / x > 2:
print("Safe division!")
else:
print("Skipped β x is zero") # This runs
# OR short-circuits on the first True
name = ""
display_name = name or "Anonymous" # "" is falsy β uses "Anonymous"
print(display_name) # Anonymous
# This pattern is very common for default values in Python:
config_value = None
setting = config_value or "default"
print(setting) # default
// AND short-circuits on the first falsy value
let x = 0;
if (x !== 0 && 10 / x > 2) {
console.log("Safe division!");
} else {
console.log("Skipped β x is zero"); // This runs
}
// OR short-circuits on the first truthy value
let name = "";
let displayName = name || "Anonymous"; // "" is falsy
console.log(displayName); // Anonymous
// Nullish coalescing (??) β a better default operator
// Only treats null and undefined as "missing" (not "" or 0)
let count = 0;
let result1 = count || 10; // 10 (0 is falsy β oops!)
let result2 = count ?? 10; // 0 (?? only catches null/undefined)
console.log(result1, result2); // 10 0
// AND short-circuits on the first false
int x = 0;
if (x != 0 && 10 / x > 2)
{
Console.WriteLine("Safe division!");
}
else
{
Console.WriteLine("Skipped β x is zero"); // This runs
}
// C# doesn't do truthy/falsy OR defaults the same way
// Instead, use the null-coalescing operator (??)
string name = null;
string displayName = name ?? "Anonymous";
Console.WriteLine(displayName); // Anonymous
// Null-coalescing works with any nullable type:
int? configValue = null;
int setting = configValue ?? 42;
Console.WriteLine(setting); // 42
β Why Short-Circuiting Matters
Short-circuit evaluation isn't just a performance trick β it's a safety feature. The x != 0 and 10 / x pattern prevents division by zero. You'll see guard clauses like this constantly in real code: check that something exists before you try to use it.
Truthiness and Falsiness
In Python and JavaScript, if statements don't require an actual boolean. They accept any value and treat it as either "truthy" (acts like true) or "falsy" (acts like false). C# is stricter β it demands real booleans.
Falsy Values β What Counts as False?
| π Python falsy values | β‘ JavaScript falsy values | π· C# approach |
|---|---|---|
False |
false |
Only bool values are allowed in conditions. You must explicitly compare: if (str.Length > 0), not if (str). |
None |
null, undefined |
|
0, 0.0 |
0, -0, 0n |
|
"" (empty string) |
"" (empty string) |
|
[] (empty list) |
NaN |
|
{} (empty dict) |
||
set() (empty set) |
||
Everything else is truthy. Non-zero numbers, non-empty strings, non-empty collections β all truthy.
# Python: empty collections and zero are falsy
items = []
if items:
print("Has items")
else:
print("Empty list!") # This runs
name = "Alice"
if name:
print(f"Hello, {name}!") # This runs β non-empty string is truthy
count = 0
if count:
print("Has items")
else:
print("Count is zero") # This runs β 0 is falsy
# Very Pythonic β check for empty/None in one step:
data = None
if not data:
print("No data available") # Catches None AND empty collections
// JavaScript: similar to Python, but with some surprises
let items = [];
if (items.length) {
console.log("Has items");
} else {
console.log("Empty array!"); // This runs
}
// β οΈ GOTCHA: empty arrays are TRUTHY in JavaScript!
if ([]) {
console.log("Empty array is truthy!"); // This runs!
}
// Check .length instead: if (items.length)
let name = "Alice";
if (name) {
console.log(`Hello, ${name}!`); // Truthy β non-empty string
}
// NaN is falsy (unique to JavaScript)
let result = parseInt("hello"); // NaN
if (result) {
console.log("Valid");
} else {
console.log("NaN is falsy!"); // This runs
}
// C#: NO implicit truthiness β must use explicit booleans
string[] items = Array.Empty<string>();
// if (items) { } // β Compile error!
if (items.Length > 0) // β
Explicit comparison required
{
Console.WriteLine("Has items");
}
else
{
Console.WriteLine("Empty array!");
}
string name = "Alice";
// if (name) { } // β Compile error!
if (!string.IsNullOrEmpty(name)) // β
Explicit check
{
Console.WriteLine($"Hello, {name}!");
}
// C#'s strictness catches bugs at compile time:
int count = 0;
// if (count) { } // β Can't use int as bool
if (count != 0) // β
Must compare explicitly
{
Console.WriteLine("Has items");
}
β οΈ JavaScript's Empty Array Gotcha
In Python, [] (empty list) is falsy. In JavaScript, [] (empty array) is truthy! This trips up developers who switch between languages. In JavaScript, always check array.length instead of using the array directly in a condition.
π Instructor Note: Delivery Guidance
The truthy/falsy table is dense. Don't try to memorize it all at once β focus on the practical takeaways: empty strings, zero, None/null are falsy; empty arrays need .length in JS. The key insight for beginners is that Python and JS let you write shorter, more expressive conditions (if name: vs. if name != ""), but C#'s strictness catches a whole category of bugs at compile time. Each approach has trade-offs. Have students predict what each value will do in an if statement β it's a great interactive exercise.
Common Truthy/Falsy Patterns
Experienced developers use truthiness constantly. Here are patterns you'll see in real code:
# Pattern 1: Default values with "or"
username = ""
display = username or "Guest"
print(display) # "Guest" (empty string is falsy)
# Pattern 2: Guard clause with "and"
user = {"name": "Alice", "email": "alice@example.com"}
email = user and user.get("email")
print(email) # "alice@example.com"
# Pattern 3: Checking if a list has items
results = [1, 2, 3]
if results:
print(f"Found {len(results)} results")
# Pattern 4: None check before use
data = None
if data is not None: # More explicit than "if data:"
process(data) # (because 0 and "" would also be falsy)
// Pattern 1: Default values with "||"
let username = "";
let display = username || "Guest";
console.log(display); // "Guest"
// Better: use ?? for defaults (doesn't treat "" and 0 as missing)
let port = 0;
let serverPort1 = port || 3000; // 3000 (0 is falsy β bug!)
let serverPort2 = port ?? 3000; // 0 (?? only catches null/undefined)
// Pattern 2: Optional chaining (?.) β safe property access
let user = { name: "Alice", address: { city: "NYC" } };
let city = user?.address?.city; // "NYC"
let zip = user?.address?.zipCode; // undefined (no error!)
// Pattern 3: Combining ?. with ??
let country = user?.address?.country ?? "Unknown";
console.log(country); // "Unknown"
// Pattern 1: Null-coalescing for defaults
string username = null;
string display = username ?? "Guest";
Console.WriteLine(display); // "Guest"
// Pattern 2: Null-conditional (?.) β safe property access
// (Similar to JavaScript's optional chaining)
string[]? items = null;
int? count = items?.Length; // null (no NullReferenceException!)
// Pattern 3: Combining ?. with ??
string? city = user?.Address?.City ?? "Unknown";
// Pattern 4: Null-coalescing assignment (??=)
string name = null;
name ??= "Default"; // Assigns "Default" only if name is null
Console.WriteLine(name); // "Default"
// Pattern 5: String-specific checks
string input = "";
if (!string.IsNullOrEmpty(input)) // Checks null AND ""
Console.WriteLine(input);
if (!string.IsNullOrWhiteSpace(input)) // Also checks " "
Console.WriteLine(input);
π‘ The ?? Operator β Available in JS and C#
JavaScript's ?? (nullish coalescing) and C#'s ?? (null-coalescing) look identical and work similarly: they provide a fallback only when the left side is null or undefined/null. Unlike ||, they don't treat 0, "", or false as "missing." Python doesn't have an equivalent operator β you'd use x if x is not None else default.
Operator Precedence
When you mix logical operators, which one gets evaluated first? The order is the same in all three languages:
| Priority | Operator | π Python | β‘ JS / π· C# |
|---|---|---|---|
| 1 (highest) | NOT | not |
! |
| 2 | AND | and |
&& |
| 3 (lowest) | OR | or |
|| |
# What does this evaluate to?
result = True or False and False
print(result) # True
# Why? AND binds tighter than OR:
# True or (False and False)
# True or False
# True
# Make it explicit with parentheses:
result = (True or False) and False
print(result) # False β now OR happens first
// What does this evaluate to?
let result = true || false && false;
console.log(result); // true
// Why? && binds tighter than ||:
// true || (false && false)
// true || false
// true
// Make it explicit:
result = (true || false) && false;
console.log(result); // false
// Same precedence rules as JavaScript
bool result = true || false && false;
Console.WriteLine(result); // True
// && binds tighter than ||:
// true || (false && false)
// true || false
// True
// Explicit:
result = (true || false) && false;
Console.WriteLine(result); // False
β Golden Rule: Use Parentheses
Don't rely on remembering precedence rules. When combining AND and OR, always use parentheses to make your intent obvious. Your future self (and your teammates) will thank you.
Exercises
ποΈ Exercise 1: Access Control Checker
Objective: Write a program that checks if a user can access a restricted resource.
Access is granted if:
- The user is an admin, OR
- The user is a member AND their account is active AND they are not banned
β Solution
role = "member"
is_active = True
is_banned = False
has_access = (
role == "admin"
or (role == "member" and is_active and not is_banned)
)
if has_access:
print("β
Access granted")
else:
print("β Access denied")
# Test with different values:
# admin β always granted
# banned member β denied
# inactive member β denied
# active, non-banned member β granted
let role = "member";
let isActive = true;
let isBanned = false;
let hasAccess =
role === "admin" ||
(role === "member" && isActive && !isBanned);
if (hasAccess) {
console.log("β
Access granted");
} else {
console.log("β Access denied");
}
string role = "member";
bool isActive = true;
bool isBanned = false;
bool hasAccess =
role == "admin" ||
(role == "member" && isActive && !isBanned);
if (hasAccess)
Console.WriteLine("β
Access granted");
else
Console.WriteLine("β Access denied");
ποΈ Exercise 2: Truthy/Falsy Predictor
Objective: For each value below, predict whether it is truthy or falsy in Python and JavaScript, then verify by running the code.
0""(empty string)"0"(string containing zero)[](empty list/array)"false"(the word "false" as a string)-1None/null
β Solution
values = [0, "", "0", [], "false", -1, None]
labels = ['0', '""', '"0"', '[]', '"false"', '-1', 'None']
print("Python Truthiness:")
print("-" * 25)
for label, val in zip(labels, values):
result = "truthy" if val else "falsy"
print(f" {label:10s} β {result}")
# Results:
# 0 β falsy
# "" β falsy
# "0" β truthy (non-empty string!)
# [] β falsy (empty list)
# "false" β truthy (non-empty string!)
# -1 β truthy (non-zero number)
# None β falsy
let values = [0, "", "0", [], "false", -1, null];
let labels = ['0', '""', '"0"', '[]', '"false"', '-1', 'null'];
console.log("JavaScript Truthiness:");
console.log("-".repeat(25));
for (let i = 0; i < values.length; i++) {
let result = values[i] ? "truthy" : "falsy";
console.log(` ${labels[i].padEnd(10)} β ${result}`);
}
// Results:
// 0 β falsy
// "" β falsy
// "0" β truthy (non-empty string!)
// [] β truthy (β οΈ DIFFERENT from Python!)
// "false" β truthy (non-empty string!)
// -1 β truthy (non-zero number)
// null β falsy
// C# doesn't have implicit truthiness β this exercise
// only applies to Python and JavaScript.
//
// In C#, you'd need explicit comparisons:
// if (number != 0) ...
// if (!string.IsNullOrEmpty(text)) ...
// if (array.Length > 0) ...
// if (obj != null) ...
//
// C# catches truthy/falsy confusion at compile time,
// which prevents the subtle bugs shown in the
// Python/JavaScript examples.
ποΈ Exercise 3: Smart Login Validator
Objective: Write a login checker that uses short-circuit evaluation. The checker should:
- Verify the username is not empty
- Verify the password is at least 8 characters
- Verify the account is not locked
- Print which check failed (using short-circuit logic)
β Solution
username = "alice"
password = "Sec123"
is_locked = False
# Check each condition and report the first failure
if not username:
print("β Username is required")
elif len(password) < 8:
print(f"β Password too short ({len(password)}/8 characters)")
elif is_locked:
print("β Account is locked β contact support")
else:
print(f"β
Welcome, {username}!")
# Alternatively, as a single expression:
is_valid = bool(username) and len(password) >= 8 and not is_locked
print(f"Login valid: {is_valid}")
let username = "alice";
let password = "Sec123";
let isLocked = false;
if (!username) {
console.log("β Username is required");
} else if (password.length < 8) {
console.log(`β Password too short (${password.length}/8 characters)`);
} else if (isLocked) {
console.log("β Account is locked β contact support");
} else {
console.log(`β
Welcome, ${username}!`);
}
let isValid = !!username && password.length >= 8 && !isLocked;
console.log(`Login valid: ${isValid}`);
string username = "alice";
string password = "Sec123";
bool isLocked = false;
if (string.IsNullOrEmpty(username))
{
Console.WriteLine("β Username is required");
}
else if (password.Length < 8)
{
Console.WriteLine($"β Password too short ({password.Length}/8 characters)");
}
else if (isLocked)
{
Console.WriteLine("β Account is locked β contact support");
}
else
{
Console.WriteLine($"β
Welcome, {username}!");
}
bool isValid = !string.IsNullOrEmpty(username)
&& password.Length >= 8
&& !isLocked;
Console.WriteLine($"Login valid: {isValid}");
π Instructor Note: Delivery Guidance
Exercise 1 is the must-do β it directly tests combining AND, OR, and NOT with proper parentheses. Exercise 2 is fantastic for interactive class time: put the values on screen and have students vote truthy/falsy before revealing the answer. The JavaScript empty-array surprise always gets a good reaction. Exercise 3 ties together short-circuiting with practical validation logic. For homework, challenge students to add more checks (e.g., password must contain uppercase, account must be verified).
Summary
π Key Takeaways
- Python uses
and,or,not; JavaScript and C# use&&,||,! - Short-circuit evaluation stops checking as soon as the result is certain β useful for safety guards like
x != 0 and 10/x - Python and JavaScript have truthy/falsy values: zero, empty strings, None/null/undefined are falsy; everything else is truthy
- Gotcha: empty arrays
[]are falsy in Python but truthy in JavaScript - C# requires explicit booleans in conditions β no implicit truthiness, which catches bugs at compile time
- The
??operator (JS and C#) provides defaults for null/undefined only, unlike||which triggers on any falsy value - AND has higher precedence than OR β but always use parentheses to make complex conditions clear
π Module 3 Complete!
You've now mastered the complete control flow toolkit: conditionals for decisions, loops for repetition, and logical operators for combining conditions. Up next: Module 4 β Functions, where you'll learn to organize your code into reusable, named blocks.
π― Quick Check
Question 1: What is the Python equivalent of JavaScript's &&?
Question 2: In JavaScript, what is the truthiness of an empty array []?