Can you write this from memory?
Write an if-else statement: - If active is True, print "item active" - Otherwise, print "item pending"
Python's if/else looks trivial... until a valid value like 0 gets treated as "missing", you accidentally rely on operator precedence, or you compare to None the wrong way.
The exercises here focus on the rules that quietly create bugs:
- truthy/falsy categories (empty containers, zero numerics, None)
- boolean operators that short-circuit and return operands (and/or)
- operator precedence (not > and > or)
- chained comparisons (0 < x < 10)
- identity vs equality (is vs ==)
Python's falsy values fall into three buckets:
| Category | Falsy values |
|---|---|
| Constants | None, False |
| Zero numerics | 0, 0.0, 0j, Decimal(0), Fraction(0, 1) |
| Empty collections | "", (), [], {}, set(), range(0) |
Everything else is truthy (including "0", "False", [0], etc.).
This catches people off guard. Python's boolean operators return one of their operands, not True/False:
# and: returns first falsy value, or last value if all truthy
0 and "hello" # → 0 (stopped at first falsy)
"hi" and "hello" # → "hello" (all truthy, returns last)
# or: returns first truthy value, or last value if all falsy
0 or "default" # → "default" (found truthy)
"hi" or "hello" # → "hi" (stopped at first truthy)
None or 0 or "" # → "" (all falsy, returns last)
Why this matters: The x or default pattern is dangerous when 0, "", or [] are valid values:
# BUG: user_count of 0 gets replaced with 1
user_count = provided_count or 1
# FIX: explicit None check
user_count = provided_count if provided_count is not None else 1
| Operator | What it checks | Use for |
|---|---|---|
is | Same object in memory | None, True, False (singletons) |
== | Values are equal | Everything else |
# Correct: is for None
if value is None:
return "missing"
# Wrong: == can be overridden by __eq__
if value == None: # works, but PEP 8 discourages
return "missing"
PEP 8: "Comparisons to singletons like None should always be done with is or is not, never the equality operators."
if x tests truthiness, not existence. This is often fine, but breaks when falsy values are valid:
# BUG: treats 0 and "" as "missing"
def get_value(x):
if x:
return x
return "default"
get_value(0) # → "default" (wrong!)
get_value("") # → "default" (wrong!)
# FIX: be explicit about what you're checking
def get_value(x):
if x is not None:
return x
return "default"
Rule of thumb: If 0, "", [], or False are valid inputs, don't use if x. This is especially important when writing Python functions with optional parameters that could legitimately be zero or empty.
not binds tighter than and, which binds tighter than or:
not a and b or c
# Is parsed as:
((not a) and b) or c
Best practice: Use parentheses whenever precedence isn't obvious at a glance.
# Hard to parse
if not is_admin and is_premium or is_trial:
...
# Clear
if (not is_admin and is_premium) or is_trial:
...
Python allows comparison chaining—it's not just syntactic sugar:
# These are equivalent:
if 0 < x < 100:
print("in range")
if 0 < x and x < 100:
print("in range")
But with chained comparisons, x is evaluated only once. This matters when x is a function call:
# get_value() called once
if 0 < get_value() < 100:
...
# get_value() called twice
if 0 < get_value() and get_value() < 100:
...
Instead of deep nesting, return early for invalid cases:
# Nested (harder to read)
def process(data):
if data is not None:
if data.is_valid:
if data.has_permission:
return do_work(data)
return None
# Guard clauses (clearer)
def process(data):
if data is None:
return None
if not data.is_valid:
return None
if not data.has_permission:
return None
return do_work(data)
When checking if any/all items match a condition, use any()/all() instead of loops:
# Instead of:
has_negative = False
for x in nums:
if x < 0:
has_negative = True
break
# Use:
has_negative = any(x < 0 for x in nums)
# Instead of:
all_positive = True
for x in nums:
if x <= 0:
all_positive = False
break
# Use:
all_positive = all(x > 0 for x in nums)
Both short-circuit (stop as soon as the answer is known). You'll see these frequently in loop conditions and comprehension filters.
For simple conditional assignments, ternary is cleaner:
# Instead of:
if condition:
result = value_if_true
else:
result = value_if_false
# Use:
result = value_if_true if condition else value_if_false
Avoid nested ternaries—they're hard to read:
# Don't do this
grade = "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "F"
# Use if/elif/else instead
Usually unnecessary and can break with truthy/falsy values:
# Redundant
if is_valid == True:
...
# Preferred
if is_valid:
...
# Exception: when you need to distinguish True from other truthy values
if flag is True: # only matches True, not 1 or "yes"
...
For complex branching based on value or structure, match is cleaner than if/elif chains:
# if/elif chain
def http_status(code):
if code == 200:
return "OK"
elif code == 404:
return "Not Found"
elif code == 500:
return "Server Error"
else:
return "Unknown"
# match/case (cleaner)
def http_status(code):
match code:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Server Error"
case _:
return "Unknown"
Pattern matching power
match does more than simple value comparison—it can destructure and match structure:
# Match multiple values
match code:
case 200 | 201 | 204:
return "Success"
case 400 | 401 | 403 | 404:
return "Client Error"
# Destructure sequences
match point:
case (0, 0):
return "origin"
case (x, 0):
return f"on x-axis at {x}"
case (0, y):
return f"on y-axis at {y}"
case (x, y):
return f"at ({x}, {y})"
# Match with guards
match user:
case {"role": "admin"}:
return "full access"
case {"role": "user", "verified": True}:
return "standard access"
case {"role": "user", "verified": False}:
return "limited access"
# Class matching
match event:
case Click(x, y) if x < 0 or y < 0:
return "out of bounds"
case Click(x, y):
return f"clicked at ({x}, {y})"
case KeyPress(key="enter"):
return "submitted"
When to use match vs if/elif:
- Use
matchwhen checking against multiple discrete values or patterns - Use
if/eliffor range checks, complex boolean logic, or Python < 3.10
The walrus operator (Python 3.8+) assigns and returns a value in one expression. It's perfect for "check if exists, then use" patterns:
# Without walrus: call function twice or use temp variable
result = expensive_operation()
if result:
use(result)
# With walrus: assign and check in one step
if (result := expensive_operation()):
use(result)
# Great for regex matching
if (match := pattern.search(text)):
print(f"Found: {match.group()}")
# Useful in comprehensions (filter + use computed value)
results = [y for x in data if (y := transform(x)) is not None]
Default with None-check
# When 0/""/[] are valid values
value = provided if provided is not None else default
# When you only want to replace None
config.get("timeout") or 30 # BUG if timeout=0 is valid
config.get("timeout", 30) # Better: use dict.get default
Conditional in comprehension
Conditionals are central to Python collections work, especially filtering and transforming data:
# Filter (if at end excludes items)
positives = [x for x in nums if x > 0]
# Transform (if-else at start transforms all)
labels = ["pos" if x > 0 else "non-pos" for x in nums]
- Truth Value Testing — falsy categories,
__bool__ - Boolean Operations — and/or short-circuit, return operands
- Comparisons — chained comparisons, is/is not
- match Statement — structural pattern matching (3.10+)
- PEP 634 — match/case specification
- PEP 8: Programming Recommendations — None checks, avoiding == True/False
When to Use Python Conditionals: Truthiness, Boolean Logic, Ternary, and Comparisons
- Branch on state, validation, or feature flags.
- Use guard clauses (early return) to keep functions readable.
- Use ternaries for short, expression-level choices.
- Use `any()` / `all()` when your intent is "any match" or "all pass".
Check Your Understanding: Python Conditionals: Truthiness, Boolean Logic, Ternary, and Comparisons
Given an optional score (may be None), return "pass" if score >= 70 else "fail".
Handle None explicitly (identity check), then compare: `if score is None: return "fail"` (or raise); otherwise `score >= 70`. Mention why `is None` is preferred and why `if score` would be wrong if score can be 0.
What You'll Practice: Python Conditionals: Truthiness, Boolean Logic, Ternary, and Comparisons
Common Python Conditionals: Truthiness, Boolean Logic, Ternary, and Comparisons Pitfalls
- Using `if x` when you really mean `if x is not None` (because 0/""/[] are falsy)
- Using `x or default` when 0/"" are valid values (you may accidentally replace them)
- Assuming `and/or` return booleans (they return operands)
- Relying on precedence in complex expressions instead of using parentheses
- Comparing to None with `==` instead of `is`
- Comparing booleans to True/False using `==`
Python Conditionals: Truthiness, Boolean Logic, Ternary, and Comparisons FAQ
What counts as falsy in Python?
Three buckets: (1) None and False, (2) zero of any numeric type (0, 0.0, 0j, Decimal(0), Fraction(0,1)), (3) empty sequences/collections like "", (), [], {}, set(), range(0). Everything else is truthy by default.
Should I write `x is None` or `x == None`?
Use `is None` / `is not None`. PEP 8 recommends identity comparisons for singletons like None, and warns that `if x` can be wrong when x may be an empty container or 0.
Do `and` / `or` return True/False?
No—`and`/`or` return one of their operands and short-circuit. This is useful for defaults, but it can also hide bugs when valid values are falsy (like 0 or "").
What's the precedence of not/and/or?
`not` binds tighter than `and`, which binds tighter than `or`. Use parentheses when it's not instantly obvious.
Is chained comparison allowed?
Yes: `x < y <= z` is valid and evaluates like `x < y and y <= z`, except `y` is evaluated only once (and it short-circuits).
Should I compare booleans to True/False?
Usually no—prefer `if cond:` or `if not cond:`. Comparing with `== True` is a common code smell and can break with truthy/falsy values.
Does Python have a switch statement?
Yes—`match`/`case` (Python 3.10+). It's more powerful than traditional switch: it does structural pattern matching, can destructure objects, and supports guards with `if`.
Python Conditionals: Truthiness, Boolean Logic, Ternary, and Comparisons Syntax Quick Reference
if not items:
print("empty")if value is None:
return "missing"name = provided if provided is not None else "Anonymous"result = "pass" if score >= 70 else "fail"if 0 < x < 100:
print("in range")if any(x < 0 for x in nums):
print("has negative")match status:
case 200: return "OK"
case 404: return "Not Found"
case _: return "Unknown"if (match := pattern.search(text)):
print(match.group())Python Conditionals: Truthiness, Boolean Logic, Ternary, and Comparisons Sample Exercises
Write an if statement that executes `pass` if `True`
if True:
passWrite an if statement that prints "Item found" if item is truthy
if item:
print("Item found")If items is empty, print "Empty items". Use a Pythonic truthiness check (not len()).
if not items:
print("Empty items")+ 32 more exercises