On this page

Operators and Control Flow

14 min read TextCh. 1 — Python Fundamentals

Operators and Control Flow

Every program needs to make decisions and repeat actions. Control flow is the mechanism that lets your code do different things depending on data — skip steps, repeat work, or choose between alternatives. In this lesson you will learn Python's operator toolkit and all of its control flow constructs, including the modern match/case statement introduced in Python 3.10.

Arithmetic Operators

You have already seen some arithmetic operators. Here is the complete picture:

a, b = 17, 5

print(a + b)   # 22  — addition
print(a - b)   # 12  — subtraction
print(a * b)   # 85  — multiplication
print(a / b)   # 3.4 — true division (always float)
print(a // b)  # 3   — floor division (integer result, rounds toward -∞)
print(a % b)   # 2   — modulo (remainder)
print(a ** b)  # 1419857 — exponentiation (17^5)

# Unary operators
print(-a)      # -17
print(+a)      # 17 (no-op, but valid)

# Augmented assignment (modify in place)
count = 0
count += 1     # count = count + 1
count *= 3     # count = count * 3
count -= 2     # count = count - 2
print(count)   # 1

Comparison Operators

Comparison operators always return a boolean (True or False):

x, y = 10, 20

print(x == y)   # False — equal
print(x != y)   # True  — not equal
print(x < y)    # True  — less than
print(x > y)    # False — greater than
print(x <= 10)  # True  — less than or equal
print(x >= 10)  # True  — greater than or equal

# Python allows chained comparisons — very readable
age = 25
print(18 <= age < 65)   # True — much cleaner than age >= 18 and age < 65

score = 85
grade = "B" if 80 <= score < 90 else "other"
print(grade)   # B

Logical Operators

Python uses the English words and, or, and not instead of symbols like &&, ||, and !:

temperature = 22
is_raining = False

# and — True only if BOTH are True
print(temperature > 20 and not is_raining)   # True — nice day!

# or — True if AT LEAST ONE is True
print(temperature < 0 or is_raining)         # False

# not — negates the boolean
print(not True)    # False
print(not False)   # True

# Short-circuit evaluation
# Python stops evaluating as soon as the result is determined
items = []
# This is safe: if items is empty (falsy), Python doesn't evaluate items[0]
first = items and items[0]
print(first)   # [] — returns the falsy value (items)

# or returns the first truthy value (or the last value if all are falsy)
default_name = None
name = default_name or "Anonymous"
print(name)    # Anonymous

Bitwise Operators

Less common but useful in certain domains (networking, flags, bit manipulation):

a, b = 0b1010, 0b1100   # 10 and 12 in binary

print(bin(a & b))   # 0b1000 — AND
print(bin(a | b))   # 0b1110 — OR
print(bin(a ^ b))   # 0b0110 — XOR
print(bin(~a))      # -0b1011 — NOT (bitwise complement)
print(bin(a << 1))  # 0b10100 — left shift
print(bin(a >> 1))  # 0b101   — right shift

Identity and Membership Operators

# Identity — checks if two names point to the same object
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)    # True  — same content
print(a is b)    # False — different objects in memory
print(a is c)    # True  — c is just another name for a

# Use `is` only for None, True, False
value = None
print(value is None)    # True (correct way)

# Membership — checks if a value is in a sequence
fruits = ["apple", "banana", "cherry"]
print("banana" in fruits)      # True
print("mango" not in fruits)   # True

text = "Hello, Python!"
print("Python" in text)        # True
print("Java" not in text)      # True

The `if/elif/else` Statement

The if statement is the most fundamental decision-making tool:

def classify_bmi(bmi: float) -> str:
    if bmi < 18.5:
        return "Underweight"
    elif bmi < 25.0:
        return "Normal weight"
    elif bmi < 30.0:
        return "Overweight"
    else:
        return "Obese"

print(classify_bmi(17.0))   # Underweight
print(classify_bmi(22.5))   # Normal weight
print(classify_bmi(27.3))   # Overweight
print(classify_bmi(35.1))   # Obese

You can have as many elif branches as you need. The else block is optional. The first condition that evaluates to True runs its block, and the rest are skipped.

Ternary (conditional) expression — a one-liner for simple if/else:

age = 20
status = "adult" if age >= 18 else "minor"
print(status)   # adult

# Nested ternary (use sparingly — can hurt readability)
score = 75
grade = "A" if score >= 90 else ("B" if score >= 80 else "C")
print(grade)    # C

The `match/case` Statement (Python 3.10+)

match/case is Python's structural pattern matching — far more powerful than a simple switch statement. It can match values, types, shapes, and even destructure data:

def http_status(status_code: int) -> str:
    match status_code:
        case 200:
            return "OK"
        case 201:
            return "Created"
        case 301 | 302:       # Pipe operator combines patterns
            return "Redirect"
        case 400:
            return "Bad Request"
        case 401 | 403:
            return "Authentication/Authorization Error"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:               # Default case (wildcard)
            return f"Unknown status: {status_code}"

print(http_status(200))   # OK
print(http_status(404))   # Not Found
print(http_status(418))   # Unknown status: 418

match/case with guards (additional conditions):

def describe_point(point: tuple[int, int]) -> str:
    match point:
        case (0, 0):
            return "Origin"
        case (x, 0):
            return f"On the X-axis at x={x}"
        case (0, y):
            return f"On the Y-axis at y={y}"
        case (x, y) if x == y:
            return f"On the diagonal at {x}"
        case (x, y):
            return f"Point at ({x}, {y})"

print(describe_point((0, 0)))    # Origin
print(describe_point((3, 0)))    # On the X-axis at x=3
print(describe_point((5, 5)))    # On the diagonal at 5
print(describe_point((2, 7)))    # Point at (2, 7)

tip type: info title: "match/case is not just switch/case"

Unlike switch/case in Java or C, Python's match/case performs structural pattern matching. It can match the shape and content of data structures like tuples, lists, and dictionaries — not just literal values. This makes it especially powerful for parsing commands, protocol messages, or API responses.

The `for` Loop

Python's for loop iterates over any iterable — a list, string, range, dictionary, file, or any object that yields values one at a time:

# Iterating over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit.upper())
# APPLE
# BANANA
# CHERRY

# Iterating over a string
for char in "Python":
    print(char, end=" ")
# P y t h o n

# range() generates a sequence of integers
for i in range(5):         # 0, 1, 2, 3, 4
    print(i, end=" ")

for i in range(1, 11):     # 1 through 10
    print(i, end=" ")

for i in range(0, 20, 3):  # 0, 3, 6, 9, 12, 15, 18 (step 3)
    print(i, end=" ")

for i in range(10, 0, -1): # 10 down to 1 (countdown)
    print(i, end=" ")

enumerate() gives you both the index and the value:

languages = ["Python", "JavaScript", "Rust", "Go"]

for index, language in enumerate(languages, start=1):
    print(f"{index}. {language}")
# 1. Python
# 2. JavaScript
# 3. Rust
# 4. Go

zip() iterates over multiple iterables in parallel:

names = ["Alice", "Bob", "Carol"]
scores = [92, 85, 78]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Alice: 92
# Bob: 85
# Carol: 78

The `while` Loop

while repeats a block as long as a condition is True:

# Countdown
count = 5
while count > 0:
    print(f"T-minus {count}...")
    count -= 1
print("Liftoff!")

# Reading input until valid (common pattern)
import random

secret = random.randint(1, 10)
attempts = 0

while True:
    guess = int(input("Guess a number (1-10): "))
    attempts += 1
    if guess == secret:
        print(f"Correct! You got it in {attempts} attempt(s).")
        break
    elif guess < secret:
        print("Too low!")
    else:
        print("Too high!")

Loop Control: `break`, `continue`, and `else`

# break — exits the loop immediately
for n in range(1, 20):
    if n % 7 == 0:
        print(f"First multiple of 7 up to 20: {n}")
        break

# continue — skips to the next iteration
print("Odd numbers from 1 to 10:")
for n in range(1, 11):
    if n % 2 == 0:
        continue     # skip even numbers
    print(n, end=" ")
# 1 3 5 7 9

# for...else and while...else
# The else block runs ONLY if the loop completed without hitting break
def find_prime(n: int) -> bool:
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            break          # factor found — not prime
    else:
        return True        # no break occurred — it's prime
    return False

print(find_prime(17))   # True
print(find_prime(18))   # False

tip type: tip title: "Avoid modifying a list while iterating over it"

Never add or remove elements from a list inside a for item in my_list: loop — this causes unpredictable skipping of elements. Instead, iterate over a copy (for item in my_list[:]) or build a new list using a list comprehension (covered in Lesson 7).

Nested Loops

# Multiplication table
for i in range(1, 6):
    for j in range(1, 6):
        print(f"{i * j:3}", end="")
    print()   # newline after each row
#   1  2  3  4  5
#   2  4  6  8 10
#   3  6  9 12 15
#   4  8 12 16 20
#   5 10 15 20 25

`pass` — The No-Op Statement

pass is a statement that does nothing. It is used as a placeholder where Python requires a statement but you have nothing to put there yet:

def future_feature() -> None:
    pass   # TODO: implement this later

for i in range(10):
    if i % 2 == 0:
        pass   # will handle even numbers later
    else:
        print(i)

nextSteps

  • functions-and-scope