Conditionals and Boolean Logic
A Boolean is a value that is either True or False. Conditionals use Booleans to decide which code runs. This guide covers every tool Python gives you for building that logic.
At a Glance
Section titled “At a Glance”| Concept | Syntax | Purpose |
|---|---|---|
| Comparison | ==, !=, <, >, <=, >= | Produce a True or False result |
| Branching | if / elif / else | Run different code depending on a condition |
| Logic | and, or, not | Combine or invert conditions |
| Truthiness | bool(value) | See how any value evaluates in a condition |
| Ternary | a if condition else b | Choose a value in one line |
| Membership | in, not in | Check whether a value is inside a container |
| Identity | is, is not | Check whether two names refer to the same object |
| Matching | match / case | Branch on the shape or value of data Python 3.10+ |
Comparison Operators
Section titled “Comparison Operators”Comparison operators evaluate to a Boolean (True or False).
| Operator | Name | Checks whether… |
|---|---|---|
== | Equal | both values are equal |
!= | Not equal | the values are different |
> | Greater than | the left value is larger |
< | Less than | the left value is smaller |
>= | Greater than or equal | the left value is larger or equal |
<= | Less than or equal | the left value is smaller or equal |
print(3 > 4) # Falseprint(3 < 4) # Trueprint(3 == 4) # Falseprint(4 == 4) # Trueprint(3 != 4) # Trueprint(3 >= 4) # Falseprint(3 <= 4) # TrueStoring Results in Variables
Section titled “Storing Results in Variables”Because comparisons produce Booleans, you can save the result and reuse it. Name Boolean variables like questions (is_adult, has_name, can_edit) so conditions read like plain English.
age = 20name = 'Sam'
is_adult = age >= 18has_name = name != ''
if is_adult and has_name: print(f'Welcome, {name}')Chained Comparisons
Section titled “Chained Comparisons”Python lets you chain comparisons the way you would write them in math.
age = 25
print(18 <= age < 65) # True, same as: 18 <= age and age < 65print(1 < 5 < 3) # FalseComparing Different Types
Section titled “Comparing Different Types”print(5 == 5.0) # True, int and float compare by valueprint('5' == 5) # False, a string never equals a numberprint('apple' < 'banana') # True, strings compare alphabeticallyprint([1, 2] == [1, 2]) # True, collections compare item by item# print('5' > 5) # TypeError: '>' not supported between 'str' and 'int'Comparing Floats
Section titled “Comparing Floats”Floating-point numbers store approximations, so avoid == for decimal results. Use math.isclose() instead.
import math
print(0.1 + 0.2 == 0.3) # Falseprint(math.isclose(0.1 + 0.2, 0.3)) # TrueConditional Statements
Section titled “Conditional Statements”Conditional statements are covered in more detail in Conditional Statements
Python branches with if, elif, and else. A block of code belongs to a condition when it is indented beneath it.
ifevaluates the first condition. If it is truthy, its block runs.elif(optional, repeatable) is checked in order only if every earlier condition failed.else(optional) runs only if nothing above matched.
age = 2
if age >= 65: print('You are a senior citizen')elif age >= 18: print('You are an adult')elif age >= 13: print('You are a teenager')else: print('You are a toddler or an infant') # RunsOnly the First Match Runs
Section titled “Only the First Match Runs”Python stops at the first branch whose condition is truthy. Order your branches from most specific to most general.
score = 95
if score >= 60: print('Pass') # Runselif score >= 90: print('Distinction') # Never runs, even though it is also trueSyntax Rules
Section titled “Syntax Rules”Blocks are defined by indentation, not brackets. The PEP 8 standard is 4 spaces per level. A missing indent raises an IndentationError.
age = 20
if age >= 18:print('Adult') # IndentationError: expected an indented blockNothing can sit between an if block and its elif or else. Doing so raises a SyntaxError.
if age >= 18: print('Adult')print('Checked') # This line splits the chainelse: # SyntaxError: invalid syntax print('Minor')pass is a placeholder that does nothing. Use it when a block is required syntactically but you are not ready to write it.
if age >= 18: pass # TODO: implement laterelse: print('Minor')Truthy and Falsy Values
Section titled “Truthy and Falsy Values”Every Python value has an inherent Boolean state when evaluated in a logical context, such as an if statement. Use the built-in bool() function to see how a value is evaluated.
| Evaluation State | Values |
|---|---|
| Falsy False State | False, None, 0, 0.0, 0j, "", b"", [], (), {}, set(), range(0) |
| Truthy True State | Everything else: non-zero numbers, non-empty strings, non-empty collections, and most objects |
# Falsyprint(bool(False)) # Falseprint(bool(None)) # Falseprint(bool(0)) # Falseprint(bool(0.0)) # Falseprint(bool('')) # Falseprint(bool([])) # Falseprint(bool({})) # False
# Truthyprint(bool(True)) # Trueprint(bool(1)) # Trueprint(bool(-5)) # True, any non-zero numberprint(bool('Hello')) # Trueprint(bool([0])) # True, the list has an item, even if that item is falsyUsing Truthiness in Conditions
Section titled “Using Truthiness in Conditions”Python applies bool() automatically inside if and while, so you rarely need to compare against empty values by hand.
items = []name = ''
# Preferredif not items: print('No items')
if name: print('Name provided')
# Works, but unnecessarily verboseif len(items) == 0: print('No items')
# Never compare a Boolean to True or Falseis_ready = Trueif is_ready: # Good passif is_ready == True: # Redundant passPitfall: None vs. Other Falsy Values
Section titled “Pitfall: None vs. Other Falsy Values”if not value treats 0, '', [], and None identically. When you specifically need to know whether a value is missing, use is None.
def describe(score): if not score: # Bug: 0 is a valid score return 'No score' return f'Score: {score}'
def describe_fixed(score): if score is None: # Only triggers when truly missing return 'No score' return f'Score: {score}'
print(describe(0)) # 'No score' (wrong)print(describe_fixed(0)) # 'Score: 0' (correct)Common Gotchas
Section titled “Common Gotchas”| Expression | Result | Why |
|---|---|---|
bool('False') | True | Any non-empty string is truthy, whatever it says |
bool('0') | True | It is a string, not the number zero |
bool(' ') | True | A space is still a character |
bool([[]]) | True | The outer list contains one item (an empty list) |
bool(float('nan')) | True | NaN is a non-zero float |
True == 1 | True | bool is a subclass of int, so True + True equals 2 |
Truthiness in Your Own Classes
Section titled “Truthiness in Your Own Classes”Custom objects are truthy by default. Define __bool__ (or __len__) to change that.
class Basket: def __init__(self, items): self.items = items
def __bool__(self): return len(self.items) > 0
print(bool(Basket([]))) # Falseprint(bool(Basket(['apple']))) # TrueBoolean Operators
Section titled “Boolean Operators”Boolean operators combine and invert conditions.
A | B | A and B | A or B |
|---|---|---|---|
True | True | True | True |
True | False | False | True |
False | True | False | True |
False | False | False | False |
not A flips the value: not True is False, and not False is True. It always returns a strict True or False.
Operators Return Operands, Not Just Booleans
Section titled “Operators Return Operands, Not Just Booleans”With non-Boolean values, and and or return one of the two operands.
A and BreturnsAif it is falsy. Otherwise it returnsB.A or BreturnsAif it is truthy. Otherwise it returnsB.
print(0 or 'default') # 'default', first truthy valueprint('' or None or 5) # 5print('hi' and 'there') # 'there', last value when all are truthyprint(0 and 'never seen') # 0, stops at the first falsy valueShort-Circuit Evaluation Optimization
Section titled “Short-Circuit Evaluation ”Operands are evaluated left to right, and evaluation stops as soon as the result is known. The right-hand side may never run.
def check(label): print(f'checking {label}') return True
print(False and check('A')) # False, check() is never calledprint(True or check('B')) # True, check() is never calledprint(True and check('C')) # prints "checking C", then TrueThis makes and a natural guard against errors:
items = []
if items and items[0] > 5: # items[0] is never touched when the list is empty print('First item is large')
user = Noneif user is not None and user.is_active: print('Active user')Fallback Values with or
Section titled “Fallback Values with or”value or default is a quick way to supply a default, but it replaces every falsy value, including legitimate ones like 0.
username = ''print(username or 'Guest') # 'Guest'
count = 0print(count or 10) # 10, surprising if 0 was a valid countprint(count if count is not None else 10) # 0, correctTesting Many Conditions with any() and all()
Section titled “Testing Many Conditions with any() and all()”scores = [72, 88, 95]
print(all(s >= 60 for s in scores)) # True, every score passesprint(any(s >= 90 for s in scores)) # True, at least one score is 90+print(all([])) # True, nothing failsprint(any([])) # False, nothing succeedsOperator Precedence
Section titled “Operator Precedence”When operators are mixed, Python evaluates them in a fixed order. Use parentheses to override it or simply to make your intent obvious.
| Priority | Operators |
|---|---|
| 1 (highest) | Comparisons, in, not in, is, is not |
| 2 | not |
| 3 | and |
| 4 | or |
| 5 (lowest) | Ternary a if c else b |
Because and binds tighter than or, the two lines below give different answers:
is_admin = Trueis_author = Falseis_verified = False
print(is_admin or is_author and is_verified) # True, is_admin or (is_author and is_verified)print((is_admin or is_author) and is_verified) # False, parentheses change the groupingWriting Clean Conditionals
Section titled “Writing Clean Conditionals”Flatten Nested Conditions
Section titled “Flatten Nested Conditions”Deeply nested blocks are hard to scan. Combine conditions with logical operators.
if is_citizen: if age >= 18: print('Can vote')if is_citizen and age >= 18: print('Can vote')Use Guard Clauses
Section titled “Use Guard Clauses”Handle invalid or edge cases first and return early. The main logic then stays at the top indentation level.
def grant_access(user): if user is not None: if user.is_active: if user.is_verified: return 'Access granted' else: return 'Unverified account' else: return 'Inactive account' else: return 'No user'def grant_access(user): if user is None: return 'No user' if not user.is_active: return 'Inactive account' if not user.is_verified: return 'Unverified account'
return 'Access granted'Ternary Expressions
Section titled “Ternary Expressions”For choosing between two values in a single line, use a conditional expression Syntactic Sugar.
variable = value_if_true if condition else value_if_falseage = 16
status = 'Adult' if age >= 18 else 'Minor'print(status) # MinorMembership Operators
Section titled “Membership Operators”in and not in check whether a value is present in a container. They work on strings, lists, tuples, sets, and dictionaries.
| Operator | Evaluates to True when… |
|---|---|
item in container | the item is found in the container |
item not in container | the item is not found in the container |
allowed_roles = ['admin', 'moderator', 'author']user_role = 'moderator'
if user_role in allowed_roles: print('Access permitted')
if 'guest' not in allowed_roles: print('Guests are unprivileged')
print('cat' in 'concatenate') # True, substring check on stringsprint('name' in {'name': 'Sam'}) # True, dictionaries check keysMembership is also the clean way to compare one value against many options:
answer = 'y'
if answer == 'y' or answer == 'yes': # Verbose print('Confirmed')
if answer in ('y', 'yes'): # Concise print('Confirmed')Identity Operators
Section titled “Identity Operators”is and is not check whether two names point to the same object in memory.
==is value equality: do they contain the same data?isis identity: are they literally the same object?
list_a = [1, 2, 3]list_b = [1, 2, 3]list_c = list_a
print(list_a == list_b) # True, same contentsprint(list_a is list_b) # False, two separate objectsprint(list_a is list_c) # True, both names refer to one object
# The idiomatic use: checking for Nonecurrent_user = Noneif current_user is None: print('No user logged in')Structural Pattern Matching Python 3.10+
Section titled “Structural Pattern Matching ”The match statement compares a value against a series of patterns and runs the first one that fits. It is a powerful alternative to long if / elif chains.
case _:is the wildcard that catches everything not matched earlier.|combines several alternatives into one pattern.
http_status = 404
match http_status: case 200 | 201: print('Success') case 400: print('Bad Request') case 404: print('Not Found') # Runs case _: print('Unknown Status Code')Destructuring and Guards
Section titled “Destructuring and Guards”Patterns can unpack sequences and dictionaries into variables. A guard (if ... after the pattern) adds an extra condition.
def describe(point): match point: case (0, 0): return 'Origin' case (x, 0): return f'On the x-axis at {x}' case (x, y) if x == y: return f'On the diagonal at {x}' case {'name': name}: return f'Named point: {name}' case _: return 'Somewhere else'
print(describe((0, 0))) # Originprint(describe((5, 0))) # On the x-axis at 5print(describe((2, 2))) # On the diagonal at 2print(describe({'name': 'A'})) # Named point: Aprint(describe((1, 2))) # Somewhere elsePitfall: Bare Names Capture
Section titled “Pitfall: Bare Names Capture”In a case, a plain name is a capture pattern: it matches anything and assigns the value to that name. It does not compare against an existing variable. To match a named constant, use a dotted name such as an Enum member.
from enum import Enum
class Color(Enum): RED = 1 GREEN = 2
def name_of(color): match color: case Color.RED: # Dotted name, compares by value return 'red' case Color.GREEN: return 'green' case _: return 'unknown'Common Mistakes
Section titled “Common Mistakes”| Mistake | Problem | Fix |
|---|---|---|
if x == 1 or 2: | Always truthy, because 2 alone is truthy | if x in (1, 2): |
if x = 5: | SyntaxError, assignment instead of comparison | if x == 5: |
if flag == True: | Redundant | if flag: |
if not value: for missing data | Also catches 0, '', and [] | if value is None: |
0.1 + 0.2 == 0.3 | Floating-point rounding | math.isclose(a, b) |
if x is 5: | Identity is not equality | if x == 5: |
value or default | Replaces valid falsy values such as 0 | value if value is not None else default |
Wrong elif order | A general condition shadows a specific one | Order from most specific to most general |
Best Practices Checklist
Section titled “Best Practices Checklist”- Rely on truthiness for emptiness checks (
if items:), and useis NonewhenNonespecifically matters. - Name Boolean variables like questions:
is_valid,has_access,can_edit. - Add parentheses whenever you mix
andwithor. - Prefer guard clauses and flattened conditions over deep nesting.
- Use
into compare one value against several options. - Reserve ternary expressions for simple, single-level choices.
- Reach for
match/casewhen branching on the shape of data, andif/eliffor general conditions.