Module 2: Control Flow

M2: Control Flow โ€” Python for Corporate Professionals | OTLMS
M2 ยท Control Flow Python for Corporate Professionals

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.

5 topics ~70 min read Foundation level All roles
๐Ÿ“–
Concept โ€” Making Decisions and Repeating Work
How Python chooses what to do and how many times to do it
โŒƒ
Topic 1

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.

The most important rule in Python: Indentation is not optional. Python uses whitespace to define code blocks. The body of an if statement must be indented โ€” 4 spaces is the standard. If your indentation is wrong, you get an IndentationError. Every code editor handles this automatically, but you need to know why it matters.

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.

Start: condition to check
โ†“
True โœ“
Run if block
False โœ—
Check elif?
โ†“
Continue with rest of program
Topic 2

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.

Topic 3

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.

โš ๏ธ Infinite loops are the main risk with while. If your condition never becomes False, your program will run forever and need to be force-quit (Ctrl+C). Always make sure something inside your loop changes the condition โ€” a counter that increments, a flag that gets set to False, or a break statement.
Topic 4

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.

KeywordWhat it doesCorporate 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
Topic 5

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.

๐Ÿ’ก A good rule of thumb: if your nesting goes deeper than two levels, consider whether the inner logic could be moved into its own function. Code that’s three or four levels deep is harder to read, harder to debug, and harder to change later. You’ll learn functions in M4.
โœ๏ธ
Syntax Reference
Every pattern from this module, annotated and runnable
โŒƒ

if / elif / else

Python
# 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

Python
# 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

Python
# 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

Python
# 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

Python
# 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}")
๐Ÿ’ก
Examples โ€” Control Flow in Real Work
Five complete programs using decisions and loops in each corporate role
โŒƒ

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.

Example 1 โ€” IT Support: SLA breach detector
IT Support

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.

Python
# 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}")
Output
Ticket Priority Age Status
————————————————
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
Example 2 โ€” Database Developer: Schema validator
Database Dev

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.

Python
# 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'}")
Output
=== Schema Validation Report ===
โœ“ Matched : 5 tables
โœ— Missing : 1 table(s)
    – timesheets
โš  Extra : 1 unexpected table(s)
    + temp_import

Overall: FAILED
Example 3 โ€” DevOps: Server health monitor
DevOpsIT Support

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.

Python
# 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.")
Output
Server         CPU   MEM  DISK   Status
———————————————-
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.
Example 4 โ€” AI / ML: Training run monitor
AI / ML

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.

Python
# 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}")
Output
Epoch     Loss   Improvement   Status
————————————————
     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
Example 5 โ€” Automation: Batch file renamer
AutomationDevOps

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.

Python
# 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)}")
Output
Processing files…

  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
๐Ÿ‹๏ธ
Practice Exercises
Four problems โ€” each one combines decisions and loops
โŒƒ

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.

1
Password strength checker. Write a program that checks a list of passwords and rates each one as Weak, Medium, or Strong. Rules: Weak = fewer than 8 characters. Medium = 8 or more characters but missing either a digit or an uppercase letter. Strong = 8+ characters AND contains at least one digit AND at least one uppercase letter. Test it with at least five passwords of varying strength.
To check for uppercase letters use any(c.isupper() for c in password) โ€” this loops through every character and returns True if any of them is uppercase. Similarly any(c.isdigit() for c in password) for digits. len(password) gives you the character count.
2
FizzBuzz for IT. Loop through the numbers 1 to 50. Print “Backup” for multiples of 3, “Restart” for multiples of 5, and “Backup-Restart” for multiples of both. For all other numbers, print the number itself. (This is the classic FizzBuzz problem โ€” it tests whether you can sequence your if/elif/else conditions correctly.)
Order your conditions carefully. Check for multiples of both 3 and 5 first (using n % 3 == 0 and n % 5 == 0) before checking for each individually. If you check for 3 first, a multiple of 15 will hit that branch and never reach the “both” case. The % operator returns the remainder โ€” if it’s 0, the number is divisible.
3
Log filter. Start with this list of log lines:
[“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.
Use continue to skip INFO and DEBUG entries. Build an empty list before the loop (alerts = []) and use alerts.append(line) inside the loop to collect the ones you want. Use enumerate(log_lines, start=1) to get line numbers alongside each entry.
4
Retry counter. Simulate a connection retry system using a while loop. Start with attempt = 1 and max_attempts = 5. Each loop iteration prints “Attempt N: connecting…”. On attempt 4, print “Connected!” and break. If the loop finishes without connecting, print “All attempts failed.” (Use an else clause on the while loop to handle the failure case.)
Python’s while (and for) loops can have an else block โ€” it runs only if the loop completed without hitting a break. Structure: while attempt <= max_attempts: … else: print(“All failed”). Inside the loop, check if attempt == 4, print success, and break.
๐Ÿ“‹
Assignment โ€” M2
A full control flow program โ€” estimated 35โ€“45 minutes
โŒƒ

๐Ÿ“‹ 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.

  1. 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).
  2. 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.
  3. After the loop, print a full table showing all online servers with their scores and statuses.
  4. Print a summary section: total servers, online vs offline count, count per environment, and how many are Healthy / Degraded / Critical.
โœ… Paste your script and full output in the comments. If your report includes the table, the summary, and the escalation logic, you’re ready for M3 โ€” Data Structures.
๐Ÿง 
Quiz โ€” Check Your Understanding
5 questions ยท instant feedback ยท retake as many times as you need
โŒƒ

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.

1. You have a for loop over a list of 10 items. On iteration 4, a break statement is hit. How many items does the loop process in total?
2. What is the key difference between using break and continue inside a loop?
3. What does range(2, 10, 2) produce?
4. You want to write a while loop that retries a connection up to 5 times. Which of the following is the safest way to avoid an infinite loop?
5. In the following code, what gets printed?

numbers = [1, 2, 3, 4, 5]
for n in numbers:
    if n % 2 == 0:
        continue
    print(n)
Loops and decisions down. Now it’s time to organise data properly.
M3: Data Structures โ€” Lists, tuples, dictionaries, sets, and nested structures.
Start M3 โ†’
Scroll to Top