Operators & Control Flow
So far your programs run straight down the page, top to bottom, doing the same thing every time. Real tests do not work that way—they make decisions (“if the banner says success, pass; otherwise fail”) and they repeat work (“try the login with each of these ten accounts”). This module gives your code a brain and a loop. You will learn the operators that compute and compare values, the if/elif/else statements that branch, and the for and while loops that repeat. These are the control structures behind every assertion and every data-driven test you will ever write.
Learning objectives
By the end of this lesson you will be able to:
- Use arithmetic, comparison, logical, and assignment operators correctly.
- Write
if,elif, andelsebranches to make decisions. - Explain truthiness and which values Python treats as
False. - Loop over a range of numbers and over items in a collection with
for. - Repeat work with
whileuntil a condition changes. - Control loops with
break,continue, andpass, and nest loops. - Write a compact decision with a ternary (conditional) expression.
Arithmetic operators
These do maths on numbers. Most are obvious, but three deserve attention.
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ - * |
Add, subtract, multiply | 3 * 4 |
12 |
/ |
True division (always a float) | 7 / 2 |
3.5 |
// |
Floor division (drops the remainder) | 7 // 2 |
3 |
% |
Modulo (the remainder) | 7 % 2 |
1 |
** |
Exponent (power) | 2 ** 3 |
8 |
The modulo operator % returns what is left over after division, and it is surprisingly useful in testing. number % 2 == 0 is the standard way to ask “is this number even?”—handy when you want to apply a step to every second record, or alternate between two test accounts.
total = 10 + 5 # 15
average = total / 2 # 7.5 (note: a float, not 7)
remainder = 17 % 5 # 2
is_even = 8 % 2 == 0 # True
Comparison operators
These compare two values and always produce a Boolean (True or False). They are the heart of every assertion: a test passes when the actual value compares as expected.
| Operator | Meaning | Example (True) |
|---|---|---|
== |
Equal to | status == 200 |
!= |
Not equal to | status != 500 |
> < |
Greater / less than | price < 100 |
>= <= |
Greater / less than or equal | count >= 1 |
actual_status = 200
print(actual_status == 200) # True -- test passes
print(actual_status != 200) # False
print(5 < 3) # False
print(5 >= 5) # True
The single most common beginner mistake is confusing = (assignment—“put this value in this variable”) with == (comparison—“are these two things equal?”). = changes a variable; == asks a question and gives back True or False.
Logical operators
To combine conditions, use and, or, and not—written as plain words, not symbols.
and—Trueonly when both sides are true.or—Truewhen at least one side is true.not— flips a Boolean:not TrueisFalse.
logged_in = True
is_admin = False
print(logged_in and is_admin) # False (both must be true)
print(logged_in or is_admin) # True (one is enough)
print(not is_admin) # True
# A realistic check: a valid test run
status = 200
body_ok = True
print(status == 200 and body_ok) # True -- response is fully healthy
Assignment operators
Beyond plain =, Python offers shortcuts that update a variable using its own current value. count += 1 means “take count, add 1, store it back”—the everyday way to increment a counter.
passed = 0
passed += 1 # same as: passed = passed + 1 -> 1
passed += 1 # 2
retries = 3
retries -= 1 # 2
timeout = 10
timeout *= 2 # 20
Making decisions: if / elif / else
An if statement runs a block only when a condition is true. Add elif (“else if”) to test further conditions, and else as a catch-all when none matched. Note the two non-negotiable pieces of syntax: a colon at the end of each condition line, and indentation of the block beneath it.
status = 404
if status == 200:
print("OK -- test passes")
elif status == 404:
print("Not Found -- resource missing")
elif status >= 500:
print("Server error -- investigate")
else:
print("Unexpected status:", status)
Python checks the conditions top to bottom and runs the block for the first one that is true, then skips the rest. The elif and else parts are optional—a bare if on its own is perfectly valid. This structure is exactly how a test classifies an outcome: pass, known failure, or something unexpected.
This is the shape of an assertion
Before you learn a real assertion library, you can hand-roll the logic a test framework automates for you:
expected_title = "Dashboard"
actual_title = "Dashboard" # imagine this came from the page
if actual_title == expected_title:
print("PASS: title matched")
else:
print("FAIL: expected", expected_title, "but got", actual_title)
Every assertion you will ever write—assert actual == expected in pytest, for example—is this pattern underneath: compare, then branch on the result. Understanding the if here means the frameworks later hold no surprises.
Truthiness
An if does not strictly need a True/False—it accepts any value and decides whether it is “truthy” or “falsy”. The following values are falsy (treated as False): the number 0, 0.0, the empty string "", the empty list [], the empty dict {}, and the special value None. Almost everything else is truthy.
results = [] # an empty list
if results:
print("We have results to report")
else:
print("No results collected yet") # this runs -- empty list is falsy
name = ""
if not name:
print("Name is missing") # this runs -- empty string is falsy
This lets you write natural checks like “if the list of errors is not empty…” as simply if errors:. It is idiomatic Python and you will read it everywhere.
Do not compare to True, and mind the chained logic
Because of truthiness, writing if logged_in == True: is redundant—just write if logged_in:. Worse, comparing to True can bite you: a value like "yes" is truthy, so if "yes": runs, but "yes" == True is False. So if "yes" == True: would not run its block, even though the string is truthy. Trust truthiness: use if value: to mean “if value is present/non-empty/non-zero,” and reserve == for comparing actual contents. This exact confusion causes tests that silently never execute their check and therefore never fail—the most dangerous kind of broken test.
The for loop
A for loop repeats a block once for each item in a sequence. It is the workhorse of data-driven testing: run the same steps for every row of test data.
Looping with range()
When you want to repeat something a set number of times, use range(). range(5) produces the numbers 0, 1, 2, 3, 4—it starts at 0 and stops before the number you give it.
for i in range(3):
print("Attempt", i)
# Attempt 0
# Attempt 1
# Attempt 2
range can also take a start and a step: range(1, 4) gives 1, 2, 3, and range(0, 10, 2) gives 0, 2, 4, 6, 8. The variable i is just a name—call it whatever reads best.
Looping over iterables
More often you loop directly over a collection—a list of usernames, say—without caring about index numbers. This is cleaner and more Pythonic than using indexes.
browsers = ["Chrome", "Firefox", "Edge"]
for browser in browsers:
print("Running suite on", browser)
# Running suite on Chrome
# Running suite on Firefox
# Running suite on Edge
Read it aloud: “for each browser in browsers.” Each time round, browser holds the next item. This is precisely how you would run the same test against several browsers or feed a list of test inputs through one check. You will meet lists in detail in Module 4.
The while loop
A while loop repeats as long as a condition stays true. Use it when you do not know in advance how many times you need to loop—for example, polling a page until something appears, or retrying until success.
attempts = 0
max_attempts = 3
connected = False
while not connected and attempts < max_attempts:
attempts += 1
print("Connection attempt", attempts)
# pretend we succeed on the third try:
if attempts == 3:
connected = True
print("Connected!" if connected else "Gave up.")
The condition is checked before each pass. The golden rule of while loops: make sure something inside the loop eventually makes the condition false—here, attempts += 1. Forget it and the loop never ends.
Tutor tip: the infinite-loop escape hatch
Sooner or later you will write a while loop whose condition never becomes false—an infinite loop—and your program will hang, printing forever or sitting silent. Do not panic and do not close the whole editor. In the terminal where it is running, press Ctrl+C. That sends an interrupt that stops the running program and hands you back the prompt. It is the universal “stop this now” for command-line programs, and you will use it often.
break, continue, and pass
Three small keywords give you fine control inside loops:
break— exit the loop immediately, skipping any remaining items.continue— skip the rest of the current pass and jump to the next item.pass— do nothing; a placeholder where Python’s syntax requires a statement but you have none yet.
accounts = ["valid1", "", "valid2", "STOP", "valid3"]
for account in accounts:
if account == "":
continue # skip blank entries, move to the next
if account == "STOP":
break # abort the whole loop here
print("Testing account:", account)
# Testing account: valid1
# Testing account: valid2
pass is different—it is a “to do” marker. When you are sketching out code and want an empty branch or function body that is syntactically legal, pass fills the gap:
if status == 418:
pass # TODO: decide how to handle "I'm a teapot" later
Nested loops
A loop can live inside another loop. The inner loop runs completely for every single pass of the outer loop—useful for testing every combination, such as each browser against each environment.
browsers = ["Chrome", "Firefox"]
environments = ["staging", "prod"]
for browser in browsers:
for env in environments:
print("Run:", browser, "on", env)
# Run: Chrome on staging
# Run: Chrome on prod
# Run: Firefox on staging
# Run: Firefox on prod
Two browsers times two environments gives four combinations—the loops produce every pairing automatically. Keep nesting shallow, though: three or four levels deep becomes hard to read, and there is usually a cleaner approach.
The ternary (conditional) expression
For a simple either/or choice you can condense an if/else into one line. The form reads almost like English: value_if_true if condition else value_if_false.
passed = True
result = "PASS" if passed else "FAIL"
print(result) # PASS
count = 0
message = "No tests" if count == 0 else "Some tests ran"
print(message) # No tests
You already saw this earlier in the while example ("Connected!" if connected else "Gave up."). Use it for short, clear choices—assigning a label, picking a message. For anything with multiple branches or real logic, a full if/elif/else stays more readable.
Hands-on exercise
Create grading.py and combine operators with control flow into a small test-result reporter:
- Make a list of integer test scores, e.g.
scores = [100, 0, 75, 90, 55]. - Loop over the scores with a
forloop. - Inside the loop, use
if/elif/elseto print"PASS"for scores>= 60and"FAIL"otherwise—plus the score. - Use
continueto skip any score of0(treat it as “not run”) and print a note instead. - Keep a running count of passes using
+=, and print the total at the end. - After the loop, use a ternary expression to print
"Suite healthy"if every non-skipped test passed, else"Needs attention". - Bonus: add a
whileloop that simulates retrying a flaky test up to three times, stopping early withbreakwhen it “passes.”
Knowledge check
- What is the difference between
/and//, and what does%return? - Why is confusing
=and==such a common bug? - Which values does Python treat as falsy? Name at least four.
- What numbers does
range(1, 6)produce? - When would you reach for a
whileloop instead of aforloop? - Explain the difference between
breakandcontinue. - Rewrite this as a ternary:
if ok: label = "up" else: label = "down".
Show answers
/is true division and always gives a float (7 / 2is3.5);//is floor division and drops the remainder (7 // 2is3);%returns the remainder (7 % 2is1).=assigns a value to a variable while==compares two values; using one where you meant the other either changes data unexpectedly or always returns the wrong Boolean.- Any four of:
0,0.0,""(empty string),[](empty list),{}(empty dict),None. 1, 2, 3, 4, 5(it stops before 6).- When you do not know in advance how many iterations are needed—e.g. retrying or polling until a condition changes.
breakexits the loop entirely;continueskips the rest of the current iteration and moves to the next item.label = "up" if ok else "down"
Key takeaways
- Operators compute and compare: arithmetic (
+ - * / // % **), comparison (== != > < >= <=), logical (and or not), and assignment shortcuts (+= -=). if/elif/elsebranch your code—watch the colon and the indentation—and this is exactly the logic behind every assertion.- Truthiness lets you write
if errors:; remember that0,"",[],{}, andNoneare falsy. forloops repeat overrange()or over a collection;whileloops repeat until a condition changes—guard against infinite loops.break,continue, andpassfine-tune loops; nested loops cover combinations; a ternary expresses a simple either/or in one line.
Go deeper
A few idioms turn clunky control flow into clean, testable code:
- Chain your comparisons. Python lets you write
0 <= score <= 100directly, instead ofscore >= 0 and score <= 100. It reads like maths and is a tidy way to assert a value sits inside an expected range. and/orreturn a value, not just a Boolean.orhands back the first truthy operand, soenv = chosen or "staging"falls back to"staging"whenchosenis empty. This short-circuiting also means the right-hand side is skipped entirely when the left already decides the result—handy for guardingobj and obj.value.
Loops have an often-missed else clause that runs only if the loop finished without hitting break—perfect for “searched everything and found nothing”:
for user in users:
if user == "admin":
print("Admin found")
break
else:
print("No admin in the list") # runs only if the loop never broke
And from Python 3.10 the match statement replaces long if/elif ladders with structural pattern matching—a cleaner way to classify something like an HTTP status:
match status:
case 200 | 201:
print("OK")
case 404:
print("Not found")
case code if code >= 500:
print("Server error", code)
case _:
print("Unexpected")