Adrian Dane

Python 30‑by‑30 Course

Module 2: Control Flow and Working With Data

You've got the basics down! This week, we'll level up by learning how to make our programs repeat tasks, handle errors without crashing, and start thinking like a problem-solver.

Contents

Quick Recap of Module 1

In Module 1, we built our foundation. Here's what we covered:

  • Setup: We installed Python and learned to use the interactive REPL (>>>) and run .py scripts.
  • Variables & I/O: We stored information in variables and used print() and input() to communicate with the user.
  • Data Types: We worked with numbers (int, float), text (str), and booleans (True/False).
  • Conditions: We made decisions in our code using if, elif, and else.
  • Collections: We learned to group data using lists [], tuples (), dictionaries {}, and sets {}.

Day 6: Smarter Decision Making

Objectives

You've already learned how to use if to make your code choose a path. Now, let's make those decisions more powerful. Often, you'll need to check multiple things at once. You can do this by combining conditions with and (both must be true) or or (at least one must be true). For example, you might give a discount if a customer is a member or if it's their birthday.

Python also lets you write more natural-looking comparisons. Instead of writing score >= 80 and score < 90, you can simply write 80 <= score < 90. It reads just like it does in maths! There's also a handy shortcut for simple if/else choices, sometimes called a "ternary operator." You can write result = 'Pass' if score >= 50 else 'Fail', which is a neat, one-line way to assign a value based on a condition.

As you build more complex programs, like figuring out shipping costs based on weight and destination, you'll find yourself "nesting" these conditions. This just means putting an if statement inside another if statement. Just be careful—if you nest too deeply, your code can become hard to read. Sometimes it's better to reorganize your logic or use a different tool, like a dictionary, to map situations to outcomes.

🤯 Conditions Getting Complicated? Let's Simplify!

Think about planning your weekend. You're using complex conditional logic without even realizing it!

"IF the weather is sunny AND my friends are free, THEN we'll go to the beach." 🏖️

  • This uses the and operator. Both conditions must be True for the plan to happen.

"ELSE IF the weather is rainy, THEN I'll stay home and watch a movie." 🍿

  • This is your elif. The first plan failed, so you check a new condition.

"ELSE I'll just go for a walk." 🚶

  • This is your else. If none of the other specific plans work out, this is your default action.

Your Python code is just making these same kinds of structured decisions.

Practice ✍️

Write a program to calculate shipping costs. Ask the user for the package weight and if they want express shipping. Rules: Standard shipping is $5. If the package is over 10kg, add $10. If express shipping is chosen, add $15. The user should only be charged one fee for weight, but express is an additional cost.

Click to reveal a sample script
# shipping_cost_calculator.py
weight = float(input("Enter package weight in kg: "))
express = input("Express shipping? (yes/no): ").lower()

cost = 5.00 # Base cost

if weight > 10:
    cost += 10.00

if express == 'yes':
    cost += 15.00

print(f"The total shipping cost is: ${cost:.2f}")
        

Day 7: Making the Computer Do the Boring Work (Loops)

Objectives

Loops are one of the biggest superpowers in programming. They let you automate repetitive tasks. Python has two main kinds. The first is the for loop, which you use when you want to go through a sequence of items, like a list or a string. For each item, the loop runs your block of code. To loop a set number of times, the range() function is your best friend. For example, for i in range(5): will run the code inside it exactly five times (with `i` being 0, 1, 2, 3, and then 4).

The second type is the while loop. This loop keeps running *as long as* a certain condition is True. It's perfect for situations where you don't know in advance how many times you'll need to repeat something, like in a menu where you keep asking for user input until they choose to 'quit'.

Sometimes you need to change a loop's behavior mid-stream. For that, you have two key commands. The break statement is your emergency exit; it stops the loop immediately, no matter what. The continue statement is like a 'skip' button; it immediately jumps to the next iteration of the loop, skipping any code that came after it for the current item.

🤯 `for` vs. `while`? Which One Do I Use?

It's all about what you know beforehand.

  • A for loop is like sending out party invitations. 💌
    You have a specific list of guests (your list or range). You go through them one by one and do the exact same thing for each: mail an invitation. You know exactly when you'll be finished—when you run out of guests.

  • A while loop is like playing a guessing game. 🤔
    You keep guessing *while* you haven't found the right number. You don't know if it will take one try or twenty tries. The loop continues based on a condition (is the guess correct?) that could change at any time.

Practice ✍️

Write a simple number guessing game. The computer should pick a secret number between 1 and 100. The user gets to guess. Use a `while` loop that continues until the user guesses correctly. After each guess, tell them if they were "Too high!" or "Too low!".

Click to reveal a sample implementation
# guessing_game.py
import random

secret_number = random.randint(1, 100)
guess = None

print("I'm thinking of a number between 1 and 100.")

while guess != secret_number:
    try:
        guess = int(input("What's your guess? "))
        if guess < secret_number:
            print("Too low!")
        elif guess > secret_number:
            print("Too high!")
    except ValueError:
        print("Please enter an actual number.")

print(f"You got it! The number was {secret_number}. 🎉")
        

Day 8: How to Write Crash-Proof Code

Objectives

Real-world programs have to deal with the unexpected. What if a user types "ten" instead of "10"? Or tries to open a file that doesn't exist? Right now, that would crash your program. Today, we learn how to make our code more robust by handling exceptions.

The main tool for this is the try...except block. You put your "risky" code inside the `try` block. If everything runs fine, the `except` block is ignored. But if an error (an exception) occurs, instead of crashing, Python immediately jumps to the `except` block and runs that code instead. This gives you a chance to print a helpful message, ask the user for input again, or just move on gracefully.

A common pattern is to wrap a `try...except` block inside a `while` loop. This allows you to keep asking the user for input until they give you something valid. You can also signal your own errors. If a function gets an argument that doesn't make sense (like a negative number for an age), you can use the raise keyword to create your own exception. This makes your code's rules clear and helps you find bugs faster.

🤯 `try/except` seems complicated. What's the big idea?

Think of try...except as having a spotter at the gym. 💪

  • You try to lift a heavy weight. This is your risky operation.
  • If you succeed, great! You put the weight down and continue. The spotter doesn't need to do anything.
  • But except if you start to fail, the spotter (the `except` block) immediately jumps in and catches the bar before it crashes on you.

It's a safety net. It lets you attempt something that *might* fail, with a guaranteed backup plan in place so the whole program doesn't come crashing down.

Practice ✍️

Create a simple calculator that asks for two numbers and an operator (+, -, *, /). Wrap the logic in a `try...except` block. You should handle two main errors: the user entering non-numeric input (ValueError) and the user trying to divide by zero (ZeroDivisionError).

Click to see a sample script
# safe_calculator.py
try:
    num1 = float(input("Enter first number: "))
    op = input("Enter operator (+, -, *, /): ")
    num2 = float(input("Enter second number: "))

    if op == '+':
        result = num1 + num2
    elif op == '-':
        result = num1 - num2
    elif op == '*':
        result = num1 * num2
    elif op == '/':
        result = num1 / num2
    else:
        result = "Invalid operator"

    print(f"The result is: {result}")

except ValueError:
    print("Error: Please enter valid numbers.")
except ZeroDivisionError:
    print("Error: You cannot divide by zero!")
        

Day 9: Thinking in Recipes (Algorithms)

Objectives

The word "algorithm" sounds intimidating, but it's just a fancy name for a step-by-step recipe to solve a problem. You use algorithms all the time, like when you follow instructions to assemble furniture or a recipe to bake a cake. In programming, it's about breaking down a large task into small, manageable steps that the computer can follow.

A great way to practice this is to write out your plan in plain English first. This is called pseudocode. Before you write a single line of Python, you might jot down notes like "1. Go through each item in the list. 2. Check if the item is what I'm looking for. 3. If it is, stop and report the position." This clarifies your thinking and makes writing the actual code much easier.

Today, we'll try this by building a simple search function. Of course, Python has built-in ways to do this that are much faster (like `list.index()`), but building it yourself is a fantastic way to understand the logic that powers those tools. Thinking algorithmically is one of the most fundamental skills in programming. It's how you go from knowing the syntax to actually solving real-world problems.

🤯 What even is an algorithm? Let's use food!

An algorithm is just a recipe. That's it. 🥪

Let's write the algorithm for making a peanut butter and jelly sandwich:

  1. Get Ingredients: bread, peanut butter, jelly.
  2. Step 1: Place two slices of bread on a plate.
  3. Step 2: Spread peanut butter on one slice.
  4. Step 3: Spread jelly on the other slice.
  5. Step 4: Place the two slices of bread together.
  6. Done. You have a sandwich.

When you write a program to sort a list or find an item, you're just writing a recipe for the computer to follow. Thinking about the problem as a series of simple, ordered steps is the heart of algorithmic thinking.

Practice ✍️

Write a function called `find_max` that takes a list of numbers and returns the largest number in the list. Do it without using the built-in `max()` function. Your "recipe" should be: create a variable to hold the max value, loop through the list, and if you find a number bigger than your current max, update it.

Click to reveal example functions
# find_max_algorithm.py
def find_max(numbers):
    # Handle the edge case of an empty list
    if not numbers:
        return None 
    
    # Step 1: Assume the first number is the biggest to start
    max_so_far = numbers[0]

    # Step 2: Loop through the rest of the numbers
    for number in numbers:
        # Step 3: If we find a bigger one, update our max
        if number > max_so_far:
            max_so_far = number
    
    # Step 4: Return the biggest one we found
    return max_so_far

# --- Test it out ---
my_scores = [78, 92, 45, 100, 88]
print(f"The highest score is: {find_max(my_scores)}") # Expected: 100

empty_list = []
print(f"The max of an empty list is: {find_max(empty_list)}") # Expected: None
        

Day 10: The Programmer's Rite of Passage: FizzBuzz

Objectives

Today we tackle a challenge that has become a classic first test for programmers: FizzBuzz. It's a simple puzzle that beautifully combines everything you've learned so far: loops, `if/elif/else` conditions, and the modulus operator (%) for checking divisibility.

Here's the task: Write a program that prints the numbers from 1 to 100. But, there's a catch:

The trickiest part is handling the numbers that are multiples of both 3 and 5 (like 15, 30, etc.). Think about the order of your `if/elif` checks. If you check for "divisible by 3" first, you'll print "Fizz" for the number 15 and never get to the "FizzBuzz" part. This puzzle teaches you to think carefully about the order of your logic. Solving it is a small but satisfying milestone!

🤯 FizzBuzz seems confusing. How should I think about it?

Forget the code for a second. Let's think about how you would do this yourself for any given number, say, 15.

You'd ask yourself a series of questions in the right order:

  1. Most specific question first: Is 15 divisible by BOTH 3 AND 5? Yes, it is. So, the answer is "FizzBuzz". Done.

Now try for the number 6:

  1. Is 6 divisible by BOTH 3 AND 5? No.
  2. Next question: Okay, is it just divisible by 3? Yes. So, the answer is "Fizz". Done.

And for the number 10:

  1. Is 10 divisible by BOTH 3 AND 5? No.
  2. Is 10 divisible by 3? No.
  3. Next question: Okay, is it just divisible by 5? Yes. The answer is "Buzz". Done.

Your if/elif/elif/else code is a direct translation of asking these questions in that specific order!

Practice ✍️

First, write your own solution to the FizzBuzz problem from 1 to 100. After you get it working, try to create a function that takes three arguments: a limit, a number for "Fizz", and a number for "Buzz", so you could run `custom_fizzbuzz(50, 4, 7)` if you wanted to!

Click to see a sample implementation
# fizzbuzz_solution.py
# The classic FizzBuzz
print("--- Classic FizzBuzz ---")
for num in range(1, 101):
    # Check for the most specific case FIRST
    if num % 3 == 0 and num % 5 == 0:
        print("FizzBuzz")
    elif num % 3 == 0:
        print("Fizz")
    elif num % 5 == 0:
        print("Buzz")
    else:
        print(num)

# A more advanced, flexible version
def custom_fizzbuzz(limit, fizz_num, buzz_num):
    print(f"\n--- Custom FizzBuzz up to {limit} ---")
    for num in range(1, limit + 1):
        output = ""
        if num % fizz_num == 0:
            output += "Fizz"
        if num % buzz_num == 0:
            output += "Buzz"
        
        if not output:
            print(num)
        else:
            print(output)

custom_fizzbuzz(30, 2, 7) # Fizz for evens, Buzz for mult of 7
        

Further resources