Control Structures
2026-08-01
Control structures, also known as compound statements, are an essential part of any programming language. They allow you to control the flow of execution in your program based on certain conditions. Python include conditional statements or selection statements, such as if; as well as looping constructs (or iteration statements) such as for and while.
Preliminaries
Several control structures requires a condition that controls behaviour. Control structures have a ‘body’ block clause, which must be indented to be considered legal syntax.
Conditions
Statements like the if statement, will either execute a branch of code, or not, depending on the value a condition, which is simply an arbitrary exprression, but with a boolean result.
We say it will conditionally execute the branch or ‘body of the if’, when the condition evaluates to True. If the condition expression is not a bool type, Python will convert it to boolean automatically first.
Blocks
The compound statements require a certain syntax. Part of that syntax, requires a block. A block in Python, is code that is indented. Several statements indented with the same amount of whitespace, is part of the block. There are no block delimiters.
You cannot arbitrarily indent lines of code. Blocks can only appear under certain conditions, like after the colon (:) part of the control structure's syntax.
Inconsistent indentation will lead to syntax errors.
Unlike some other languages, Python's blocks do not automatically introduce a nested scope. This can be confusing, but only when you forget that all names are just entries in dictionaries. Python simulates traditional scope with dictionaries, but basic blocks to not naturally have a nested dictionary.
The blocks of functions and classes, do have their own nested dictionaries, so that we can still have the concept of namespaces and local variables.
If Statement
The if statement will execute its block or ‘execution branch’, only when the required condition is True. If the block consists of one simple statement, it can appear on the same line as the if, but that is not considered good practice.
if condition: statement
if condition:
block
py — If statement conditions
>> if True: print("TRUE") #→ TRUE
>> if False: print("TRUE") #→
>> if True:
·· print("TRUE") #→ TRUE
·· print("Still TRUE") #→ Still TRUE
>> if "ABC": print("TRUE") #→ TRUE
>> if 12345: print("TRUE") #→ TRUEThe condition is never ‘hard-coded’ as we did above. It will normally contain comparison and logical operators. The code example below checks first if height is not empty (empty string will be converted to False). Then it shows two ways to check if the input height is in the range 1…10 inclusive.
py — More if statement conditions
>> height = input("Height [1..10]?: ")
>> if not height:
·· print("height is empty")
>> height = input("Height [1..10]?: ")
>> if 1 <= height and height <= 10:
·· print("height is OK")
>> if 1 <= height <= 10:
·· print("height is OK")The last if uses a Python shorthand for the previous if.
Do not confuse an if statement with the if in a conditional expression.
Else Clause
The if statement above will either take a branch, or not. Execution continues in either situation after the statement. We can provide an alternate or ‘false’ branch, if the ‘true’ branch was not taken. This requires the optional else clause.
if condition:
true-block
else:
false-block
py — Else clause example
>> if True:
·· print("TRUE")
·· else:
·· print("FALSE") #→ TRUE
>> print("AFTER") #→ AFTER
>> if False:
·· print("TRUE")
·· else:
·· print("FALSE") #→ FALSE
>> print("AFTER") #→ AFTERThe else cannot appear by itself. This is not the only place where it can be used. We shall see it again later.
Elif Clause
We can create a control structure that selects one of multiple branches, not just two — often called a multi-way select statement. This requires a variation of the if statement called elif (short for ‘else if’). An if statement may have multiple such elif clauses. Each elif must have its own condition.
if condition₁:
block₁
elif condition₂:
block₂
···
else:
false-block
The else clause is still optional, and if present, must appear last. Its false-block will only be executed if all the conditions above it, resulted in False.
While Loop
The while statement is an iteration statement or ‘loop’. It executes its body block while a condition remains True (or: until the condition becomes False).
while condition₁:
block₁
Apart from the keyword, it has the same structure as the if statement above. You should make sure that the condition becomes False at some point, otherwise the iteration will continue indefinitely. Such a loop is called an infinite loop, which is seldom desirable.
py — While loop with descending numbers
>> i = 5
>> while i > 0:
·· print(i, end=' ') #→ 5 4 3 2 1
·· i = i - 1 #← (1)
>> print(i) #→ 0(1)— Here we ensure that the conditioni > 0will eventually become False.
If you wanted to output to be with ascending numbers, you can write the iteration as follows:
py — While loop with ascending numbers
>> i = 0
>> while i < 5:
·· print(i + 1, end=' ') #→ 1 2 3 4 5
·· i = i + 1 #← (1)
>> print(i) #→ 0For Loop
This statement should have been called the ‘foreach statement’, and that is how you should think about it. Although spelled wrong, the for statement iterates through a sequence (a fancy name for a collection of items).
for item
in sequence:
block
Abstractly, this construct implements “for each item in a collection sequence…”. It iterates through a collection of items, one item per iteration.
Since strings (type str expressions) are sequences, we can use this to iterate through the characters in a string.
py — Iterate through characters in string
>> for c in "ABCDE":
·· print(f"({c})", end='') #→(A)(B)(C)(D)(E)It becomes even more useful, if we consider the range function; it produces a range object, which is documented as being a sequence. We can reproduce the logic in a prior while statement example, using for this time:
py — For loop with range generator
>> for i in range(5):
·· print(i + 1, end=' ') #→ 1 2 3 4 5
>> print(i) #→ 5First recognise that the name i is
available (in scope) after the loop has terminated. Secondly: when we
know the number of iterations required, the for
is more compact than an equivalent while,
and should be preferred.
Break & Continue
The break and continue statements are execution transfer statements. They summarily transfer execution away from the normal flow of control. This means that no statement after an execution transfer statement, will execute.
As a consequence, these statements are only really useful after a conditional statement such as if. Furthermore, they are constrained to the blocks of iteration statements like for and while.
Break Statement
The break statement will ‘jump out’ of an iteration statement; immediately, regardless of the state of the iteration and a possible condition.
The following example will repeatedly request a height from the user, while it is not valid. When the user entered something, the break statement will terminate the loop:
py — Deliberate infinite loop for repeated input
>> while True:
·· height = input("Height?: ")
·· if height:
·· break
·· print("Try again.")
·· # use `height` here...This is an example of a deliberate infinite loop that is actually useful. It is better than the alternative, which must duplicate the input part:
py — Inefficient repeated input loop
>> height = input("Height?: ")
>> while not height:
·· height = input("Height?: ")
·· if height:
·· break
·· print("Try again.")
·· # use `height` here...Do not use break indiscriminately. For patterns like the prior example above, it is easy to recognise and understand, and will not earn you the ire of your colleagues.
Loop Else
An interesting Python feature, is that the else can also appear at then end of any of the iteration statements, for and while:
for ‖ while …
:
block₁
else:
block₂
The code in block₂ will only run if the loop in block₁ finished without being interrupted by a break. This is similar to using a flag variable called broke to check if the loop ended normally or was broken out of, as we do in the following example:
py — Check if loop terminated with break
>> broke = False
>> for i in range(5):
·· if i == 3:
·· broke = True
·· break
·· print(i, end=' ') #→ 0 1 2
>> if not broke:
·· print("Normal stop") #← will not run.Using the else clause would eliminate the need for the broke flag, without affecting the behaviour:
py — Else clause on for loop
>> for i in range(5):
·· if i == 3:
·· break
·· print(i, end=' ') #→ 0 1 2
>> else:
·· print("Normal stop") #← will not run.And that is the only reason else is allowed after iteration statements — a nice convenience when you need it, otherwise you just ignore it.
Continue Statement
The continue statement ‘performs the next iteration’ immediately, instead of when the flow reach the normal end of the block. Like break, it is really only useful after an if statement. It can simplify some code, mostly in the reduction of indentation levels.
Here is an example script snippet that calculates the sum of squares of a list of positive numbers. It ignores negative numbers, and does not use continue.
py — Sum of squares of positive numbers
nums = [1, -2, 3, -4, 5, -6, 7]
sosq = 0
for num in nums:
if num >= 0:
sosq += num ** 2
print(f"Sum of squares: {sosq}")Notice that the ‘work’ sosq += num ** 2 is three levels
deep. Now we rewrite the above code extract, using continue:
py — Next iteration with continue
nums = [1, -2, 3, -4, 5, -6, 7]
sosq = 0
for num in nums:
if num < 0: continue
sosq += num ** 2
print(f"Sum of squares: {sosq}")Using continue can save indentation levels and make code neater. However, like break, continue changes the normal flow of control and can make complex code harder to follow. Use it carefully — always consider readability first.
Pattern Matching
PEP-634 contains the formal specification for structural pattern matching, while PEP-636 is a tutorial. This Python 3.10 syntax provides a new match statement, which is vaguely similar to ‘switch’ statements as found in some C-like languages.
py
— some structural pattern matching
examples
value = "something" #←or use `input(…)`
### Match constants, including strings, using `|` for alternation.
match value.lower():
case "one thing":
print("One Thing")
case "some thing" | "something":
print("Some Thing")
case "other" | "other thing":
print("Other Thing")
case _: #←‘wildcard’; matches anything.
print("Or Another Thing")
errors = [200, 202, 300, 400, 405, 500, 600]
### Match constants, or bind to variable (`e`) and include `if`
for error in errors:
match error:
case e if 100 <= e < 200:
print(f"{error}: Informational status")
case 200:
print(f"{error}: Absolute success")
case e if 200 < e < 300:
print(f"{error}: Generally successful")
case 300:
print(f"{error}: Specific redirection")
case e if 300 < e < 400:
print(f"{error}: Other redirection")
case e if 400 <= e < 500:
print(f"{error}: Some request error")
case e if 500 <= e < 600:
print(f"{error}: Some server error")
case _:
print(f"{error}: Unrecognised error")The last example would arguably more readable with a series of if-elif statements, but structural pattern matching can get much fancier than these simple examples.
You can bind a value to a name and use it in the corresponding code block:
py — Binding names in match statements
def greet(who):
match who:
case {"name": str(name), "age": int(age)}:
print(f"Hi, {name}! ({age} years old)")
case _:
print("Invalid `who` data")
greet({"name": "Shane", "age": 24}) #→ Hi, Shane! (24 years old)The first case
bounds name (of type string)
and age (of type int) to
the values passed in the dictionary, so that is can be used with the print
that follows. It could have been any names.
You can match sequences, such as lists or tuples, with a specified structure:
py — Matching sequences
def describe(colors):
match colors:
case []:
print("No colors")
case [single]:
print(f"One color: {single}")
case [first, second]:
print(f"Two colors: {first}, {second}")
case [_, *others]:
print(f"Multiple: {', '.join(others)}")
describe(["red", "green", "blue"]) #→ Multiple: green, blueGuards are additional conditions that must be satisfied for a case to be considered a match. You can add a guard to a case statement by using the if keyword:
py — If guards in cases
def process_number(number):
match number:
case x if x < 0:
print("Negative number")
case x if x == 0:
print("Zero")
case x if x > 0:
print("Positive number")
process_number(42) #→ Positive numberThis also creates the value x as a binding for
number, in case you wanted to use it in the case
blocks.
To simulate the ‘default’ or ‘catch-all’ situation as found in other languages, we use ‘case **_:**’. It must appear last for it to be useful. If no such catch-all case is present, and none of the other cases matched, nothing will be executed in the match statement.