Python control flow
last modified October 30, 2026 Python 3.15
In this article we show how to control the flow of a Python program.
Control flow
When a Python program is run, the code is executed from top to bottom. The
flow of the program can be altered with various keywords, including
if/elif/else, for,
while, break, continue,
match, and try.
The control flow structures are used to execute code conditionally or repeatedly. A block of code that belongs to a statement is defined by its indentation; there are no braces.
The if statement
The if keyword is followed by an expression and a colon. If the
expression is true, the indented block below it is executed.
temperatures = [12, -3, 25, 0]
for t in temperatures:
if t > 20:
print(f'{t} °C: warm')
print(' take sunglasses')
print('done')
We go over a list of temperatures and print a message for the warm ones.
if t > 20:
print(f'{t} °C: warm')
print(' take sunglasses')
Both indented lines form the block of the if statement. For
temperatures that are not above 20, nothing is done. The final
print is not indented under the loop, so it runs once at the
end.
$ python if_statement.py 25 °C: warm take sunglasses done
The else clause
The else clause creates a second branch. If the expression after
if is false, the block after else is executed.
for n in [7, 10, -4, 0]:
if n % 2 == 0:
print(f'{n} is even')
else:
print(f'{n} is odd')
Exactly one of the two blocks runs for each number. The remainder operator
% tells us whether a number is even.
$ python if_else.py 7 is odd 10 is even -4 is even 0 is even
The elif clause
We create multiple branches with the elif keyword. Each condition
is tested only if all previous conditions were false. The optional
else block runs when none of the conditions match.
def grade(score):
if score >= 90:
return 'A'
elif score >= 80:
return 'B'
elif score >= 70:
return 'C'
elif score >= 60:
return 'D'
else:
return 'F'
for score in [95, 83, 70, 61, 42]:
print(score, grade(score))
The grade function turns a score into a letter grade.
if score >= 90:
return 'A'
elif score >= 80:
return 'B'
The order of the tests matters. A score of 95 also satisfies
score >= 80, but the first true condition wins and the rest
are skipped. That is why the second test does not need an upper bound.
$ python elif_chain.py 95 A 83 B 70 C 61 D 42 F
Conditional expressions
A conditional expression, sometimes called the ternary operator, chooses
between two values in a single expression: x if condition else
y.
age = 17
status = 'adult' if age >= 18 else 'minor'
print(status)
items = 1
print(f'{items} item{"" if items == 1 else "s"}')
values = [3, -1, 4, -5]
print([v if v > 0 else 0 for v in values])
Conditional expressions are handy in assignments, f-strings, and comprehensions.
print(f'{items} item{"" if items == 1 else "s"}')
We choose between the singular and plural form of a word.
print([v if v > 0 else 0 for v in values])
In a comprehension, a conditional expression transforms each element; here, negative values are replaced with zero.
$ python conditional_expr.py minor 1 item [3, 0, 4, 0]
Truthiness
The condition does not have to be a boolean. Every Python object is either
truthy or falsy. Zero numbers, empty containers and strings,
None, and False are falsy; everything else is
truthy.
values = [0, 0.0, '', [], {}, None, False, 1, 'no', [0], ' ']
for val in values:
if val:
print(f'{val!r:>6} is truthy')
else:
print(f'{val!r:>6} is falsy')
We test a list of values directly in an if statement.
if val:
This is the idiomatic way to check for an empty list or string; there is no
need to write if len(val) > 0. Note that [0] and
' ' are truthy, because they are not empty.
$ python truthiness.py
0 is falsy
0.0 is falsy
'' is falsy
[] is falsy
{} is falsy
None is falsy
False is falsy
1 is truthy
'no' is truthy
[0] is truthy
' ' is truthy
Boolean operators and chained comparisons
Conditions are combined with and, or, and
not. The and and or operators
short-circuit: the right operand is evaluated only when it is needed.
age = 34
if 18 <= age < 65:
print('working age')
def check(name, result):
print(f'checking {name}')
return result
if check('a', False) and check('b', True):
print('both')
if check('c', True) or check('d', True):
print('at least one')
name = '' or 'guest'
print(name)
if not []:
print('empty list')
The check function prints its name, so we can see which
operands are evaluated.
if 18 <= age < 65:
Comparisons can be chained. The expression means 18 <= age and age
< 65.
if check('a', False) and check('b', True):
Because the left operand of and is false, the whole expression is
false and check('b') is never called. Likewise, the true left
operand of or makes check('d') unnecessary.
name = '' or 'guest'
The or operator returns one of its operands, not
True or False. This gives a short way to provide a
default for an empty value.
$ python boolean_ops.py working age checking a checking c at least one guest empty list
The while loop
The while keyword creates a loop. The block is executed again
and again as long as the condition is true.
numbers = [22, 34, 12, 32, 4]
total = 0
i = 0
while i < len(numbers):
total += numbers[i]
i += 1
print('The sum is:', total)
n = 27
steps = 0
while n != 1:
n = n // 2 if n % 2 == 0 else 3 * n + 1
steps += 1
print('Collatz steps for 27:', steps)
The example contains two while loops.
while i < len(numbers):
total += numbers[i]
i += 1
The first loop sums the numbers of a list with an index variable. A
for loop or the sum built-in would be simpler here;
while is the right tool when we do not know the number of
iterations in advance.
while n != 1:
n = n // 2 if n % 2 == 0 else 3 * n + 1
steps += 1
The second loop counts the steps of the Collatz sequence, whose length cannot be computed beforehand.
$ python while_loop.py The sum is: 104 Collatz steps for 27: 111
The break statement
The break statement terminates the innermost loop immediately.
import random
while True:
val = random.randint(1, 30)
print(val, end=' ')
if val == 22:
break
print()
We print random integers until we get 22. The while True loop
would run forever; break is the only way out. The output differs
from run to run.
$ python break_loop.py 6 1 3 24 26 26 26 6 30 17 30 17 10 27 10 1 11 7 21 15 4 1 13 11 24 5 20 8 24 24 11 2 16 24 17 16 28 7 8 26 16 26 20 11 6 22
The continue statement
The continue statement skips the rest of the current iteration
and starts the next one.
num = 0
while num < 30:
num += 1
if num % 3 == 0:
continue
print(num, end=' ')
print()
lines = ['host = localhost', '', '# comment', 'port = 8080']
for line in lines:
if not line or line.startswith('#'):
continue
key, value = line.split(' = ')
print(key, '->', value)
The first loop prints numbers up to 30 that are not divisible by three.
for line in lines:
if not line or line.startswith('#'):
continue
key, value = line.split(' = ')
The second loop shows a common use: skipping empty lines and comments early keeps the main code of the loop unindented.
$ python continue_loop.py 1 2 4 5 7 8 10 11 13 14 16 17 19 20 22 23 25 26 28 29 host -> localhost port -> 8080
The for loop
The for loop iterates over the items of any iterable: strings,
lists, ranges, dictionaries, files, generators, and more.
for c in 'sky':
print(c, end=' ')
print()
for i in range(2, 11, 3):
print(i, end=' ')
print()
fruits = ['apple', 'banana', 'cherry']
for idx, fruit in enumerate(fruits, start=1):
print(idx, fruit)
prices = [1.2, 0.5, 3.0]
for fruit, price in zip(fruits, prices):
print(f'{fruit}: {price:.2f}')
capitals = {'Slovakia': 'Bratislava', 'Hungary': 'Budapest'}
for country, capital in capitals.items():
print(f'{capital} is the capital of {country}')
The example shows the most common forms of the for loop.
for i in range(2, 11, 3):
The range function generates numbers from 2 up to, but not
including, 11 with step 3.
for idx, fruit in enumerate(fruits, start=1):
The enumerate function gives us an index together with each
item; there is no need for a counter variable.
for fruit, price in zip(fruits, prices):
The zip function walks over several iterables in parallel.
for country, capital in capitals.items():
The items method of a dictionary yields key/value pairs.
The for statement is covered in more detail in
Python for loop.
$ python for_loop.py s k y 2 5 8 1 apple 2 banana 3 cherry apple: 1.20 banana: 0.50 cherry: 3.00 Bratislava is the capital of Slovakia Budapest is the capital of Hungary
The else clause of loops
Both for and while loops can have an
else clause. It runs when the loop finishes normally, but not
when it is terminated with break. It is best read as "no
break".
def find_divisor(n):
for d in range(2, int(n ** 0.5) + 1):
if n % d == 0:
print(f'{n} is divisible by {d}')
break
else:
print(f'{n} is prime')
for n in [91, 97, 1001, 7919]:
find_divisor(n)
def connect(replies, limit=3):
attempt = 0
while attempt < limit:
reply = replies[attempt]
attempt += 1
if reply == 'ok':
print(f'connected after {attempt} attempts')
break
else:
print(f'gave up after {limit} attempts')
connect(['timeout', 'ok'])
connect(['timeout', 'timeout', 'refused'])
We use the clause in two typical search situations.
for d in range(2, int(n ** 0.5) + 1):
if n % d == 0:
print(f'{n} is divisible by {d}')
break
else:
print(f'{n} is prime')
We look for a divisor. If one is found, the loop breaks. If the loop runs out
of candidates, the else block reports a prime. Note that the
else is aligned with for, not with
if.
while attempt < limit:
...
if reply == 'ok':
print(f'connected after {attempt} attempts')
break
else:
print(f'gave up after {limit} attempts')
The while version retries an operation. The else
block handles the case where all attempts failed, without an extra flag
variable.
$ python loop_else.py 91 is divisible by 7 97 is prime 1001 is divisible by 7 7919 is prime connected after 2 attempts gave up after 3 attempts
Leaving nested loops
The break statement leaves only the innermost loop. To leave
several loops at once, we can put them in a function and use
return.
grid = [
[1, 4, 7],
[2, 9, 3],
[8, 5, 6],
]
def find(grid, target):
for r, row in enumerate(grid):
for c, val in enumerate(row):
if val == target:
return r, c
return None
print(find(grid, 9))
print(find(grid, 10))
found = None
for r, row in enumerate(grid):
for c, val in enumerate(row):
if val == 9:
found = (r, c)
break
if found:
break
print(found)
We search a two-dimensional grid for a value.
for r, row in enumerate(grid):
for c, val in enumerate(row):
if val == target:
return r, c
return None
The return statement ends both loops and the function. If the
loops finish without a match, the function returns None.
if found:
break
Without a function, we need a variable that the outer loop checks after the inner loop ends. The function version is shorter and clearer.
$ python nested_break.py (1, 1) None (1, 1)
The walrus operator
The assignment expression :=, called the walrus operator, assigns
a value and returns it in one expression. It is often used in loop and
if conditions.
import io
stream = io.BytesIO(b'abcdefghijklmnopqrstuvw')
while chunk := stream.read(5):
print(chunk)
data = [4, 18, 7, 25, 3]
if (n := len(data)) > 3:
print(f'list is too long ({n} elements)')
print([y for x in data if (y := x * 2) > 10])
We read a stream in chunks and reuse computed values.
while chunk := stream.read(5):
print(chunk)
We read five bytes, store them in chunk, and test them in one
step. The loop stops when read returns an empty, falsy bytes
object at the end of the stream.
if (n := len(data)) > 3:
The length is computed once and is available inside the block. The
parentheses are needed because := binds less tightly than
>.
$ python walrus.py b'abcde' b'fghij' b'klmno' b'pqrst' b'uvw' list is too long (5 elements) [36, 14, 50]
The pass statement
A block cannot be empty. The pass statement does nothing and
serves as a placeholder where a statement is syntactically required.
class NotReady(Exception):
pass
def todo():
pass
for n in range(3):
pass
print(todo())
print(n)
try:
raise NotReady('later')
except NotReady:
pass
print('finished')
We use pass in a class, a function, a loop, and an exception
handler.
try:
raise NotReady('later')
except NotReady:
pass
An empty except block silences an exception. Do this only for
exceptions that are truly expected; silencing all errors hides bugs.
$ python pass_stmt.py None 2 finished
The match statement
The match statement compares a value against a series of
patterns and executes the block of the first matching case.
grades = ['A', 'B', 'C', 'D', 'E', 'F', 'FX']
for grade in grades:
match grade:
case 'A' | 'B' | 'C' | 'D' | 'E':
print(grade, 'passed')
case 'F' | 'FX':
print(grade, 'failed')
We have a list of grades. Grades A to E pass the exam, while F and FX fail it.
match grade:
case 'A' | 'B' | 'C' | 'D' | 'E':
print(grade, 'passed')
case 'F' | 'FX':
print(grade, 'failed')
The | operator combines several alternatives in one pattern.
Unlike the switch statement of C-like languages, there is no
fall-through and no break is needed.
$ python match_stmt.py A passed B passed C passed D passed E passed F failed FX failed
Matching structure
The real strength of match is matching the shape of data and
binding parts of it to variables.
def handle(command):
match command.split():
case ['go', direction] if direction in ('north', 'south', 'east', 'west'):
return f'moving {direction}'
case ['go', _]:
return 'unknown direction'
case ['take', *items] if items:
return f'taking {", ".join(items)}'
case ['quit' | 'exit']:
return 'bye'
case []:
return 'say something'
case _:
return f'unknown command: {command!r}'
for cmd in ['go north', 'go up', 'take key lamp', 'exit', '', 'dance']:
print(f'{cmd!r:16} -> {handle(cmd)}')
We parse commands of a text adventure game.
case ['go', direction] if direction in ('north', 'south', 'east', 'west'):
The pattern matches a list of exactly two words, the first being
go. The second word is bound to direction. The
if guard adds an extra condition.
case ['take', *items] if items:
A starred name collects the remaining elements into a list.
case ['quit' | 'exit']: ... case _:
Alternatives can be nested inside a pattern. The wildcard _
matches anything and acts as the default branch.
Pattern matching is covered in more detail in Python pattern match.
$ python match_patterns.py 'go north' -> moving north 'go up' -> unknown direction 'take key lamp' -> taking key, lamp 'exit' -> bye '' -> say something 'dance' -> unknown command: 'dance'
try, except, else, and finally
Exceptions also change the flow of a program. The try statement
has four parts: the guarded code, handlers for exceptions, an
else block that runs when no exception occurred, and a
finally block that runs in every case.
def parse(text):
try:
value = int(text)
except ValueError:
print(f'{text!r}: not a number')
return None
else:
print(f'{text!r}: parsed {value}')
return value
finally:
print(' cleanup')
parse('42')
parse('forty')
The parse function converts text to an integer.
else:
print(f'{text!r}: parsed {value}')
return value
finally:
print(' cleanup')
The else block keeps the success path out of the
try block, so that exceptions raised there are not caught by
accident. The finally block runs even though both branches
leave the function with return.
$ python try_flow.py '42': parsed 42 cleanup 'forty': not a number cleanup
except without parentheses
Since Python 3.14 (PEP 758), several exception types in an
except clause can be listed without parentheses, as long as
there is no as clause.
def convert(value):
try:
return int(value)
except ValueError, TypeError:
return None
print(convert('12'))
print(convert('x'))
print(convert(None))
The convert function returns None for values that
cannot be converted.
except ValueError, TypeError:
int('x') raises a ValueError and
int(None) a TypeError; both are handled. Older
versions reject this syntax, as the run with Python 3.13 shows.
$ python except_comma.py
12
None
None
$ python3.13 except_comma.py
File "/home/user/python/except_comma.py", line 4
except ValueError, TypeError:
^^^^^^^^^^^^^^^^^^^^^
SyntaxError: multiple exception types must be parenthesized
return, break, and continue in finally
A return, break, or continue inside a
finally block silently discards any exception that is being
raised. Since Python 3.14 (PEP 765), the compiler emits a
SyntaxWarning for such code.
def risky():
try:
raise ValueError('important error')
finally:
return 'finally wins'
print(risky())
The ValueError is lost, because the return in the
finally block replaces it with a normal return value.
finally:
return 'finally wins'
The program prints the same result with all versions, but Python 3.14 and 3.15 warn about the hidden bug. Python 3.13 runs it silently.
$ python finally_return.py /home/user/python/finally_return.py:5: SyntaxWarning: 'return' in a 'finally' block return 'finally wins' finally wins $ python3.14 finally_return.py /home/user/python/finally_return.py:5: SyntaxWarning: 'return' in a 'finally' block return 'finally wins' finally wins $ python3.13 finally_return.py finally wins
Source
More Control Flow Tools - Python tutorial
In this article we have worked with program control flow in Python.
Author
List all Python tutorials.