Skip to content

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.

ConceptSyntaxPurpose
Comparison==, !=, <, >, <=, >=Produce a True or False result
Branchingif / elif / elseRun different code depending on a condition
Logicand, or, notCombine or invert conditions
Truthinessbool(value)See how any value evaluates in a condition
Ternarya if condition else bChoose a value in one line
Membershipin, not inCheck whether a value is inside a container
Identityis, is notCheck whether two names refer to the same object
Matchingmatch / caseBranch on the shape or value of data Python 3.10+

Comparison operators evaluate to a Boolean (True or False).

OperatorNameChecks whether…
==Equalboth values are equal
!=Not equalthe values are different
>Greater thanthe left value is larger
<Less thanthe left value is smaller
>=Greater than or equalthe left value is larger or equal
<=Less than or equalthe left value is smaller or equal
comparison_operators.py
print(3 > 4) # False
print(3 < 4) # True
print(3 == 4) # False
print(4 == 4) # True
print(3 != 4) # True
print(3 >= 4) # False
print(3 <= 4) # True

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.

boolean_variables.py
age = 20
name = 'Sam'
is_adult = age >= 18
has_name = name != ''
if is_adult and has_name:
print(f'Welcome, {name}')

Python lets you chain comparisons the way you would write them in math.

chained_comparisons.py
age = 25
print(18 <= age < 65) # True, same as: 18 <= age and age < 65
print(1 < 5 < 3) # False
comparing_types.py
print(5 == 5.0) # True, int and float compare by value
print('5' == 5) # False, a string never equals a number
print('apple' < 'banana') # True, strings compare alphabetically
print([1, 2] == [1, 2]) # True, collections compare item by item
# print('5' > 5) # TypeError: '>' not supported between 'str' and 'int'

Floating-point numbers store approximations, so avoid == for decimal results. Use math.isclose() instead.

float_comparison.py
import math
print(0.1 + 0.2 == 0.3) # False
print(math.isclose(0.1 + 0.2, 0.3)) # True

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.

  1. if evaluates the first condition. If it is truthy, its block runs.
  2. elif (optional, repeatable) is checked in order only if every earlier condition failed.
  3. else (optional) runs only if nothing above matched.
control_flow.py
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') # Runs

Python stops at the first branch whose condition is truthy. Order your branches from most specific to most general.

first_match_wins.py
score = 95
if score >= 60:
print('Pass') # Runs
elif score >= 90:
print('Distinction') # Never runs, even though it is also true

Blocks are defined by indentation, not brackets. The PEP 8 standard is 4 spaces per level. A missing indent raises an IndentationError.

indentation_error.py
age = 20
if age >= 18:
print('Adult') # IndentationError: expected an indented block

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 StateValues
Falsy False StateFalse, None, 0, 0.0, 0j, "", b"", [], (), {}, set(), range(0)
Truthy True StateEverything else: non-zero numbers, non-empty strings, non-empty collections, and most objects
boolean_evaluation.py
# Falsy
print(bool(False)) # False
print(bool(None)) # False
print(bool(0)) # False
print(bool(0.0)) # False
print(bool('')) # False
print(bool([])) # False
print(bool({})) # False
# Truthy
print(bool(True)) # True
print(bool(1)) # True
print(bool(-5)) # True, any non-zero number
print(bool('Hello')) # True
print(bool([0])) # True, the list has an item, even if that item is falsy

Python applies bool() automatically inside if and while, so you rarely need to compare against empty values by hand.

idiomatic_checks.py
items = []
name = ''
# Preferred
if not items:
print('No items')
if name:
print('Name provided')
# Works, but unnecessarily verbose
if len(items) == 0:
print('No items')
# Never compare a Boolean to True or False
is_ready = True
if is_ready: # Good
pass
if is_ready == True: # Redundant
pass

if not value treats 0, '', [], and None identically. When you specifically need to know whether a value is missing, use is None.

none_vs_falsy.py
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)
ExpressionResultWhy
bool('False')TrueAny non-empty string is truthy, whatever it says
bool('0')TrueIt is a string, not the number zero
bool(' ')TrueA space is still a character
bool([[]])TrueThe outer list contains one item (an empty list)
bool(float('nan'))TrueNaN is a non-zero float
True == 1Truebool is a subclass of int, so True + True equals 2

Custom objects are truthy by default. Define __bool__ (or __len__) to change that.

custom_truthiness.py
class Basket:
def __init__(self, items):
self.items = items
def __bool__(self):
return len(self.items) > 0
print(bool(Basket([]))) # False
print(bool(Basket(['apple']))) # True

Boolean operators combine and invert conditions.

ABA and BA or B
TrueTrueTrueTrue
TrueFalseFalseTrue
FalseTrueFalseTrue
FalseFalseFalseFalse

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 B returns A if it is falsy. Otherwise it returns B.
  • A or B returns A if it is truthy. Otherwise it returns B.
operand_results.py
print(0 or 'default') # 'default', first truthy value
print('' or None or 5) # 5
print('hi' and 'there') # 'there', last value when all are truthy
print(0 and 'never seen') # 0, stops at the first falsy value

Short-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.

short_circuit.py
def check(label):
print(f'checking {label}')
return True
print(False and check('A')) # False, check() is never called
print(True or check('B')) # True, check() is never called
print(True and check('C')) # prints "checking C", then True

This makes and a natural guard against errors:

guard_with_and.py
items = []
if items and items[0] > 5: # items[0] is never touched when the list is empty
print('First item is large')
user = None
if user is not None and user.is_active:
print('Active user')

value or default is a quick way to supply a default, but it replaces every falsy value, including legitimate ones like 0.

or_default.py
username = ''
print(username or 'Guest') # 'Guest'
count = 0
print(count or 10) # 10, surprising if 0 was a valid count
print(count if count is not None else 10) # 0, correct

Testing Many Conditions with any() and all()

Section titled “Testing Many Conditions with any() and all()”
any_all.py
scores = [72, 88, 95]
print(all(s >= 60 for s in scores)) # True, every score passes
print(any(s >= 90 for s in scores)) # True, at least one score is 90+
print(all([])) # True, nothing fails
print(any([])) # False, nothing succeeds

When operators are mixed, Python evaluates them in a fixed order. Use parentheses to override it or simply to make your intent obvious.

PriorityOperators
1 (highest)Comparisons, in, not in, is, is not
2not
3and
4or
5 (lowest)Ternary a if c else b

Because and binds tighter than or, the two lines below give different answers:

operator_precedence.py
is_admin = True
is_author = False
is_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 grouping

Deeply nested blocks are hard to scan. Combine conditions with logical operators.

if is_citizen:
if age >= 18:
print('Can vote')

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'

For choosing between two values in a single line, use a conditional expression Syntactic Sugar.

ternary_syntax.py
variable = value_if_true if condition else value_if_false
ternary_expressions.py
age = 16
status = 'Adult' if age >= 18 else 'Minor'
print(status) # Minor

in and not in check whether a value is present in a container. They work on strings, lists, tuples, sets, and dictionaries.

OperatorEvaluates to True when…
item in containerthe item is found in the container
item not in containerthe item is not found in the container
membership_operators.py
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 strings
print('name' in {'name': 'Sam'}) # True, dictionaries check keys

Membership is also the clean way to compare one value against many options:

multiple_options.py
answer = 'y'
if answer == 'y' or answer == 'yes': # Verbose
print('Confirmed')
if answer in ('y', 'yes'): # Concise
print('Confirmed')

is and is not check whether two names point to the same object in memory.

  • == is value equality: do they contain the same data?
  • is is identity: are they literally the same object?
identity_operators.py
list_a = [1, 2, 3]
list_b = [1, 2, 3]
list_c = list_a
print(list_a == list_b) # True, same contents
print(list_a is list_b) # False, two separate objects
print(list_a is list_c) # True, both names refer to one object
# The idiomatic use: checking for None
current_user = None
if 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.
pattern_matching.py
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')

Patterns can unpack sequences and dictionaries into variables. A guard (if ... after the pattern) adds an extra condition.

match_patterns.py
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))) # Origin
print(describe((5, 0))) # On the x-axis at 5
print(describe((2, 2))) # On the diagonal at 2
print(describe({'name': 'A'})) # Named point: A
print(describe((1, 2))) # Somewhere else

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.

match_constants.py
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'

MistakeProblemFix
if x == 1 or 2:Always truthy, because 2 alone is truthyif x in (1, 2):
if x = 5:SyntaxError, assignment instead of comparisonif x == 5:
if flag == True:Redundantif flag:
if not value: for missing dataAlso catches 0, '', and []if value is None:
0.1 + 0.2 == 0.3Floating-point roundingmath.isclose(a, b)
if x is 5:Identity is not equalityif x == 5:
value or defaultReplaces valid falsy values such as 0value if value is not None else default
Wrong elif orderA general condition shadows a specific oneOrder from most specific to most general
  1. Rely on truthiness for emptiness checks (if items:), and use is None when None specifically matters.
  2. Name Boolean variables like questions: is_valid, has_access, can_edit.
  3. Add parentheses whenever you mix and with or.
  4. Prefer guard clauses and flattened conditions over deep nesting.
  5. Use in to compare one value against several options.
  6. Reserve ternary expressions for simple, single-level choices.
  7. Reach for match / case when branching on the shape of data, and if / elif for general conditions.