Python course Part 2: Logic & Loops (Making Your Code Think)

In Part 1 we built a tip calculator that ran top to bottom. Useful, but completely linear — it couldn't make a single decision, and if the user typed something unexpected, the whole thing crashed.
Real software needs logic. It has to evaluate conditions, repeat work, and react to whatever the world throws at it. By the end of this post we'll have built a smart number-guessing game that tracks attempts and gives dynamic feedback.
Here's the target:
Welcome to the Terminal Hacker System.
I have generated a secure passcode between 1 and 100.
You have 5 attempts to bypass the firewall.
Attempt 1: Enter your guess: 50
[!] Too low.
Attempt 2: Enter your guess: 75
[!] Too high.
Attempt 3: Enter your guess: 63
[SUCCESS] Firewall bypassed in 3 attempts!
Let's introduce the brains of Python.
🛑 Dev Callout: Indentation Is Law
Coming from C#, .NET, or Java? You're used to marking code blocks with curly braces
{}and ending statements with semicolons;. Python throws both out.A block starts with a colon
:, and everything indented under it (four spaces, by convention) belongs to that block. The moment you un-indent, the block ends. There's no compiler flag for this — it is the syntax. The upside: badly-formatted Python literally won't run, so every Python codebase you ever read is forced to be visually consistent.
1. Booleans and Conditionals (if, elif, else)
At the core of all logic sits the boolean: True or False (always capitalized in Python). You produce booleans with comparison operators — ==, !=, >, <, >=, <= — and use them to branch:
system_status = "offline"
battery_level = 15
# Note the colon and the indentation on each branch:
if system_status == "online":
print("All systems nominal.")
elif battery_level < 20:
print("Warning: low power. Initiating sleep mode.")
else:
print("System is offline, but power is stable.")
elif is Python's "else if" — you can chain as many as you like. Python checks each condition top to bottom and runs the first one that's True.
Truthy and falsy. Python is elegant about emptiness. You rarely need to write if name != "" or if len(items) > 0. Empty things — "", 0, [], None — are treated as False. Everything else is True:
user_input = ""
if not user_input:
print("You didn't type anything!")
2. The while Loop
A while loop runs as long as its condition stays True. It's perfect for menus, game loops, or anything that should keep going until a specific event happens:
security_breach = False
attempts = 0
while not security_breach:
attempts += 1
print(f"Scanning for vulnerabilities... (scan {attempts})")
if attempts == 3:
print("Breach detected! Shutting down.")
security_breach = True # this flips the condition and ends the loop
That attempts += 1 is shorthand for attempts = attempts + 1. You'll use it constantly.
Two keywords give you finer control inside any loop:
break— bail out of the loop immediately.continue— skip the rest of this iteration and jump back to the top.
3. The for Loop
🛑 Dev Callout: The End of
for (int i = 0; ...)If you're used to writing
for (int i = 0; i < collection.Length; i++), you'll like this. Python'sforloop behaves like aforeach— it iterates directly over the items in a collection, not over an index counter you have to manage by hand. Off-by-one errors mostly disappear.
When you want to walk over a string, a list, or just repeat something a fixed number of times, reach for for. To repeat N times, pair it with the built-in range():
# Iterate over the characters in a string
for letter in "HACK":
print(f"Decrypting character: {letter}")
# Repeat an action 5 times.
# range(5) produces 0, 1, 2, 3, 4 — five numbers, starting at zero.
for i in range(5):
print(f"Ping request {i + 1} sent.")
4. Bringing It Together: The Number-Guessing Game
Now we combine while, if/elif/else, and user input into the game we previewed.
To pick a secret number, we need our first piece of the standard library: the random module. We'll cover modules properly later — for now, import random at the top of the file gives us random.randint().
Create a file named firewall_bypass.py:
import random
# 1. Set up the game
secret_passcode = random.randint(1, 100) # a random whole number, 1 to 100 inclusive
max_attempts = 5
current_attempt = 0
firewall_bypassed = False
print("Welcome to the Terminal Hacker System.")
print("I have generated a secure passcode between 1 and 100.")
print(f"You have {max_attempts} attempts to bypass the firewall.\n")
# 2. The main game loop — runs until the player is out of attempts
while current_attempt < max_attempts:
current_attempt += 1
# We'll assume the player types a real number for now.
# (Handling "twenty" without crashing is exactly what Part 5 is about.)
guess = int(input(f"Attempt {current_attempt}: Enter your guess: "))
# 3. Compare the guess to the secret
if guess == secret_passcode:
print(f"[SUCCESS] Firewall bypassed in {current_attempt} attempts!")
firewall_bypassed = True
break # we won — leave the loop now
elif guess < secret_passcode:
print("[!] Too low.\n")
else:
print("[!] Too high.\n")
# 4. Handle the loss.
# If the loop ran out of attempts and we never set firewall_bypassed to True:
if not firewall_bypassed:
print(f"[LOCKED] You failed. The correct passcode was {secret_passcode}.")
Run it a few times. Because the passcode is random, no two games play the same — and a smart player can crack any code in this range within the 5 attempts using a halving strategy. (More on why that works in Part 3's Big-O callout.)
What's Next?
Your program can now make decisions and repeat work. But it still only ever holds a handful of single values in named variables.
What happens when you need to track 10,000 compromised IP addresses, or map user IDs to permission levels? Creating enemy_1_health, enemy_2_health, and so on is a fast track to madness. In Part 3: The Ultimate Guide to Python Data Structures, we unlock Lists, Tuples, Sets, and Dictionaries — the containers that hold collections of data.