if / elif / else, Loops, and Loop Control
Up to now, your programs ran top to bottom โ every line, every time. Control flow changes that. It lets your code make decisions, repeat actions, and skip steps based on conditions. This is where Python starts to feel like automation rather than just a calculator.
if / elif / else โ Making Decisions
Every useful program needs to make decisions. If a disk is over 90% full, send an alert. If a ticket priority is High, escalate it. If a user’s login fails three times, lock the account. That kind of conditional logic is written in Python using if, elif, and else.
The structure is simple: you write if, then a condition that evaluates to either True or False, then a colon. Everything indented below that line runs only when the condition is true. The elif (short for “else if”) lets you check a second condition if the first was false. The else is the catch-all โ it runs if none of the conditions above it were true.
You can nest if statements inside other if statements, but keep it to a maximum of two or three levels deep before your code becomes hard to read. If you find yourself deeply nesting conditions, that’s usually a sign the logic can be restructured.
for Loops โ Repeating Over a Sequence
A for loop repeats a block of code once for each item in a sequence. The sequence can be a list of servers, a range of numbers, the characters in a string, or anything else Python can iterate over. You’ll use for loops constantly โ checking every server in a list, processing every row in a file, or sending an email to every address in a team.
The loop variable (the name you put after for) takes on the value of each item in turn. You can call it anything, but by convention you name it something meaningful: server when you’re looping over servers, ticket when looping over tickets, i when it’s just a counter.
range() is the built-in function for generating a sequence of numbers. range(5) gives you 0, 1, 2, 3, 4. range(1, 6) gives 1 through 5. range(0, 100, 10) gives 0, 10, 20 … 90. The pattern is always range(start, stop, step) โ and the stop value is always excluded.
while Loops โ Repeating Until a Condition Changes
A while loop keeps running as long as its condition stays True. Unlike a for loop โ which has a definite number of iterations โ a while loop runs indefinitely until something inside the loop changes the condition to False.
You’d use a while loop when you don’t know in advance how many times you need to repeat something. Common examples: retrying a network connection until it succeeds, reading lines from a file until there are no more, or waiting for a process to finish before continuing.
break, continue, pass โ Loop Control
These three keywords give you fine-grained control over what happens inside a loop.
break exits the loop immediately โ the remaining iterations are abandoned. Use it when you’ve found what you’re looking for and there’s no point continuing. For example: once you find the first server that’s offline, break โ you’ve got your answer.
continue skips the rest of the current iteration and jumps straight to the next one. The loop itself keeps running. Use it to filter out items you want to ignore without stopping the whole loop. For example: skip any log entry that isn’t an error, process the rest.
pass does nothing at all. It’s a placeholder โ Python requires something in a code block, and pass satisfies that requirement without actually doing anything. You’ll use it when you’re planning the structure of your code and want to come back and fill in the logic later.
| Keyword | What it does | Corporate use case |
|---|---|---|
| break | Exit the loop immediately | Stop scanning servers once the first critical one is found |
| continue | Skip this iteration, move to next | Skip INFO log entries, only process ERROR and WARNING lines |
| pass | Do nothing โ placeholder only | Stub out a branch you’ll implement later without breaking the script |
Nested Conditions & Loops
You can put loops inside loops, and if statements inside loops (or vice versa). This is called nesting. A nested loop runs its inner loop completely for each iteration of the outer loop.
In corporate work, nested loops appear when you need to check every item against every condition โ for example, checking each server in each region, or each user across each permission level. They’re powerful but can be slow on large data sets, so use them deliberately.
if / elif / else
# Basic if / elif / else
cpu_usage = 87.5
if cpu_usage >= 90:
print("CRITICAL โ CPU above 90%")
elif cpu_usage >= 75:
print("WARNING โ CPU above 75%")
elif cpu_usage >= 50:
print("NOTICE โ CPU above 50%")
else:
print("OK โ CPU is healthy")
# Output: WARNING โ CPU above 75%
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Combining conditions with and / or / not
disk_pct = 92
is_production = True
if disk_pct > 90 and is_production:
print("Alert: production disk critical")
if disk_pct > 90 or cpu_usage > 90:
print("At least one resource in danger")
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Checking string values
status = "offline"
if status == "online":
print("Server reachable")
elif status == "offline":
print("Server unreachable โ escalating")
else:
print(f"Unknown status: {status}")
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# in operator โ check membership
priority = "High"
if priority in ["High", "Critical"]:
print("Escalate immediately")
# Inline / ternary style (one-liners for simple cases)
label = "urgent" if priority == "Critical" else "normal"
for loops
# Loop over a list
servers = ["web-01", "db-01", "api-01"]
for server in servers:
print(f"Checking {server}...")
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# range() โ loop a fixed number of times
for i in range(5): # 0 1 2 3 4
print(i)
for i in range(1, 6): # 1 2 3 4 5
print(i)
for i in range(0, 100, 25): # 0 25 50 75
print(i)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# enumerate() โ loop with index AND value
for idx, server in enumerate(servers, start=1):
print(f"{idx}. {server}")
# 1. web-01
# 2. db-01
# 3. api-01
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Loop over a string character by character
hostname = "web-01"
for char in hostname:
print(char, end="") # end="" stops the newline
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Accumulator pattern โ build a total inside a loop
response_times = [312, 287, 445, 298, 501]
total = 0
for t in response_times:
total += t
average = total / len(response_times)
print(f"Average: {average:.1f} ms") # Average: 368.6 ms
while loops
# Basic while loop with counter
attempt = 1
max_attempts = 3
while attempt <= max_attempts:
print(f"Connection attempt {attempt}...")
attempt += 1 # CRITICAL: always update the counter
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# while with a flag variable
connected = False
tries = 0
while not connected and tries < 5:
print(f"Trying to connect... (attempt {tries + 1})")
tries += 1
if tries == 3: # simulate success on 3rd try
connected = True
if connected:
print("Connected successfully.")
else:
print("Connection failed after 5 attempts.")
break, continue, pass
# break โ exit loop when condition is met
servers = ["web-01", "db-01", "api-01", "cache-01"]
offline_servers = ["db-01", "cache-01"]
for server in servers:
if server in offline_servers:
print(f"First offline server found: {server}")
break
# First offline server found: db-01
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# continue โ skip unwanted items
log_lines = [
"INFO: Service started",
"ERROR: Disk at 94%",
"INFO: Health check OK",
"WARNING: Memory at 82%",
]
for line in log_lines:
if line.startswith("INFO"):
continue # skip info lines
print(f"ALERT: {line}")
# ALERT: ERROR: Disk at 94%
# ALERT: WARNING: Memory at 82%
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# pass โ placeholder for future logic
for server in servers:
if server.startswith("cache"):
pass # TODO: add cache-specific check
else:
print(f"Standard check: {server}")
Nested loops and conditions
# Nested loop: check each server in each region
regions = ["us-east", "eu-west"]
services = ["api", "db", "cache"]
for region in regions:
print(f"\nRegion: {region}")
for service in services:
print(f" Checking {service} in {region}...")
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# if inside for โ filter and categorise
ticket_counts = {"web-01": 3, "db-01": 0, "api-01": 11, "cache-01": 7}
for server, count in ticket_counts.items():
if count == 0:
status = "โ Clear"
elif count <= 5:
status = "โ Review"
else:
status = "โ Escalate"
print(f"{server:12} {count:>3} tickets {status}")
These examples are more substantial than M1’s โ each program does something genuinely useful, using only what you’ve learned across M0, M1, and M2. No libraries, no functions yet. Just variables, strings, conditions, and loops doing real work.
Loops through a list of open tickets, checks each one’s age against SLA thresholds, categorises it, and prints a summary report. This is exactly what a helpdesk lead checks each morning before the team standup.
# IT Support: Check tickets against SLA thresholds
tickets = [
{"id": "TKT-1041", "priority": "High", "age_hrs": 5},
{"id": "TKT-1035", "priority": "Critical", "age_hrs": 2},
{"id": "TKT-1029", "priority": "Medium", "age_hrs": 26},
{"id": "TKT-1018", "priority": "High", "age_hrs": 9},
{"id": "TKT-1011", "priority": "Low", "age_hrs": 72},
]
# SLA limits in hours
sla = {"Critical": 1, "High": 8, "Medium": 24, "Low": 72}
breached = 0
at_risk = 0
ok_count = 0
print(f"{'Ticket':12} {'Priority':10} {'Age':>5} {'Status'}")
print("-" * 48)
for t in tickets:
limit = sla[t["priority"]]
age = t["age_hrs"]
pct_used = (age / limit) * 100
if age > limit:
status = "โ BREACHED"
breached += 1
elif pct_used >= 80:
status = "โ AT RISK"
at_risk += 1
else:
status = "โ OK"
ok_count += 1
print(f"{t['id']:12} {t['priority']:10} {age:>4}h {status}")
print("-" * 48)
print(f"Breached: {breached} At risk: {at_risk} OK: {ok_count}")
————————————————
TKT-1041 High 5h โ OK
TKT-1035 Critical 2h โ BREACHED
TKT-1029 Medium 26h โ BREACHED
TKT-1018 High 9h โ BREACHED
TKT-1011 Low 72h โ AT RISK
————————————————
Breached: 3 At risk: 1 OK: 1
Checks a list of expected table names against what’s actually in the database, flags missing and extra tables, and produces a clean validation report. DBAs run checks like this before and after migrations.
# Database: Compare expected vs actual schema tables
expected_tables = [
"employees", "departments", "salaries",
"projects", "timesheets", "audit_log"
]
# Simulating what a DB query would return
actual_tables = [
"employees", "departments", "salaries",
"projects", "audit_log", "temp_import"
]
missing = []
extra = []
matched = []
# Find missing tables
for table in expected_tables:
if table in actual_tables:
matched.append(table)
else:
missing.append(table)
# Find unexpected extra tables
for table in actual_tables:
if table not in expected_tables:
extra.append(table)
print("=== Schema Validation Report ===")
print(f"โ Matched : {len(matched)} tables")
if missing:
print(f"โ Missing : {len(missing)} table(s)")
for t in missing:
print(f" - {t}")
if extra:
print(f"โ Extra : {len(extra)} unexpected table(s)")
for t in extra:
print(f" + {t}")
validation_passed = not missing and not extra
print(f"\nOverall: {'PASSED' if validation_passed else 'FAILED'}")
โ Matched : 5 tables
โ Missing : 1 table(s)
– timesheets
โ Extra : 1 unexpected table(s)
+ temp_import
Overall: FAILED
Loops through a list of servers with their metrics, categorises each server’s health, and stops early if a CRITICAL server is found โ printing an escalation alert immediately. This is the logic behind a real monitoring script.
# DevOps: Server health monitor with early exit
servers = [
{"name": "web-01", "cpu": 45, "mem": 62, "disk": 71},
{"name": "web-02", "cpu": 78, "mem": 80, "disk": 55},
{"name": "db-01", "cpu": 92, "mem": 95, "disk": 88},
{"name": "api-01", "cpu": 34, "mem": 41, "disk": 50},
{"name": "cache-01", "cpu": 20, "mem": 55, "disk": 40},
]
critical_found = False
print(f"{'Server':12} {'CPU':>5} {'MEM':>5} {'DISK':>5} {'Status'}")
print("-" * 46)
for s in servers:
# Determine status based on highest metric
worst = max(s["cpu"], s["mem"], s["disk"])
if worst >= 90:
status = "๐ด CRITICAL"
critical_found = True
elif worst >= 75:
status = "๐ก WARNING"
else:
status = "๐ข OK"
print(f"{s['name']:12} {s['cpu']:>4}% {s['mem']:>4}% {s['disk']:>4}% {status}")
if critical_found:
print(f"\nโ ESCALATION: {s['name']} is critical โ paging on-call engineer.")
break
if not critical_found:
print("\nAll servers checked. No critical issues.")
———————————————-
web-01 45% 62% 71% ๐ข OK
web-02 78% 80% 55% ๐ก WARNING
db-01 92% 95% 88% ๐ด CRITICAL
โ ESCALATION: db-01 is critical โ paging on-call engineer.
Simulates iterating through training epochs, checks loss after each epoch, and stops early if the model converges. Early stopping is a standard technique in ML training โ this shows exactly how the decision logic works.
# AI/ML: Epoch training loop with early stopping
# Simulated loss values per epoch (normally from a model)
epoch_losses = [0.842, 0.631, 0.489, 0.372, 0.298,
0.251, 0.248, 0.246, 0.245, 0.244]
convergence_threshold = 0.005 # stop if improvement < this
best_loss = 999
patience = 3 # stop after 3 epochs of no improvement
no_improve_count = 0
print(f"{'Epoch':>6} {'Loss':>8} {'Improvement':>13} {'Status'}")
print("-" * 48)
for epoch, loss in enumerate(epoch_losses, start=1):
improvement = best_loss - loss
if improvement > convergence_threshold:
status = "improving"
best_loss = loss
no_improve_count = 0
else:
no_improve_count += 1
status = f"no change ({no_improve_count}/{patience})"
print(f"{epoch:>6} {loss:>8.3f} {improvement:>+13.4f} {status}")
if no_improve_count >= patience:
print(f"\nEarly stopping at epoch {epoch}. Best loss: {best_loss:.3f}")
break
else:
print(f"\nTraining complete. Final loss: {best_loss:.3f}")
————————————————
1 0.842 -998.1580 improving
2 0.631 +0.2110 improving
3 0.489 +0.1420 improving
4 0.372 +0.1170 improving
5 0.298 +0.0740 improving
6 0.251 +0.0470 improving
7 0.248 +0.0030 no change (1/3)
8 0.246 +0.0020 no change (2/3)
9 0.245 +0.0010 no change (3/3)
Early stopping at epoch 9. Best loss: 0.251
Takes a list of raw file names, validates each one, skips invalid entries, renames valid files to a standardised format, and produces a summary. This is the logic inside any file-processing automation script.
# Automation: Batch rename files to a standardised format
raw_files = [
"Sales Report Q1.xlsx",
"invoice_2026_03.pdf",
"DRAFT budget v2.docx",
"", # empty โ should be skipped
"HR Policy Update.pdf",
"temp___.csv", # starts with temp โ skip
"Q2 Forecast Final.xlsx",
]
renamed = []
skipped = []
print("Processing files...\n")
for filename in raw_files:
# Skip empty entries
if not filename.strip():
skipped.append("(empty)")
continue
# Skip temp files
if filename.lower().startswith("temp"):
skipped.append(filename)
print(f" SKIP {filename} (temp file)")
continue
# Build standardised name
name, ext = filename.rsplit(".", 1)
clean = name.strip().lower()
clean = clean.replace(" ", "_")
new_name = f"{clean}.{ext.lower()}"
renamed.append(new_name)
print(f" OK {filename:30} โ {new_name}")
print(f"\nDone. Renamed: {len(renamed)} Skipped: {len(skipped)}")
OK Sales Report Q1.xlsx โ sales_report_q1.xlsx
OK invoice_2026_03.pdf โ invoice_2026_03.pdf
OK DRAFT budget v2.docx โ draft_budget_v2.docx
SKIP temp___.csv (temp file)
OK HR Policy Update.pdf โ hr_policy_update.pdf
OK Q2 Forecast Final.xlsx โ q2_forecast_final.xlsx
Done. Renamed: 5 Skipped: 2
Save each exercise as a separate .py file and run it. These are more challenging than M1 exercises โ each one requires you to combine if logic inside a loop. If you get stuck, read the hint, then try to write the solution yourself rather than copying it directly.
[“INFO: startup complete”, “ERROR: db timeout”, “WARNING: high memory”, “INFO: request OK”, “ERROR: disk full”, “DEBUG: cache miss”]
Write a loop that skips all INFO and DEBUG lines, collects all ERROR and WARNING lines into a separate list, prints each alert with a line number, and prints the total count at the end.
๐ Server Fleet Health Report
Your company runs a fleet of servers across three environments: production, staging, and dev. Write a Python script called fleet_report.py that analyses the fleet and produces a formatted health report.
- Create a list of at least 8 servers. Each server is a dictionary with these keys: name (str), env (str โ “production”, “staging”, or “dev”), cpu (int), memory (int), disk (int), online (bool).
-
Loop through the fleet. For each server:
- Skip it entirely if online is False โ use continue and add it to an offline_servers list.
- Calculate a health score: start at 100, subtract 1 point for every percentage of CPU above 60, subtract 1 for every percentage of memory above 70, subtract 2 for every percentage of disk above 80.
- Assign a status: score โฅ 80 = “Healthy”, score โฅ 60 = “Degraded”, below 60 = “Critical”.
- If the server is in production and status is “Critical”, immediately print a escalation alert and use break.
- After the loop, print a full table showing all online servers with their scores and statuses.
- Print a summary section: total servers, online vs offline count, count per environment, and how many are Healthy / Degraded / Critical.
These questions test your understanding of how control flow works โ not just syntax, but the logic behind it. Think through each one carefully before selecting.
numbers = [1, 2, 3, 4, 5]
for n in numbers:
if n % 2 == 0:
continue
print(n)
