Control flow lets your code make decisions and repeat actions. These concepts power everything from training loops that iterate millions of times to conditionals that check model performanceβessential patterns in every AI codebase.
Conditionals let your program make decisions based on conditions. The basic structure
is if, optionally followed by elif (else if) and else.
accuracy = 0.92
if accuracy >= 0.95:
print("Excellent model!")
elif accuracy >= 0.90:
print("Good model")
elif accuracy >= 0.80:
print("Decent model")
else:
print("Needs improvement")
# Output: Good model
Python uses indentation (4 spaces) to define code blocks. Unlike other languages that use braces {}, Python requires consistent indentation. Mixing tabs and spaces causes errors!
epoch = 50
loss = 0.05
patience = 10
epochs_without_improvement = 12
# Combining conditions with 'and', 'or', 'not'
if loss < 0.1 and epoch > 10:
print("Training converging well")
if loss > 1.0 or epochs_without_improvement > patience:
print("Consider stopping training")
if not (loss < 0.01):
print("Loss hasn't reached target yet")
model = None
data = []
# Check for None
if model is None:
print("Model not loaded")
# Check for empty (falsy values)
if not data:
print("No data available")
# Check if value in collection
supported_models = ["gpt-4", "claude", "llama"]
if "gpt-4" in supported_models:
print("GPT-4 is supported")
Conditionals are everywhere: checking if GPU is available, early stopping when validation loss stops improving, switching between training and evaluation mode, handling different input formats, and implementing learning rate schedules.
For loops iterate over sequences (lists, strings, ranges, etc.). They're the most common loop type in AI codeβused for training epochs, processing batches, and more.
# Iterating over a list
models = ["BERT", "GPT", "T5"]
for model in models:
print(f"Testing {model}")
# Using range() for counting
for epoch in range(5): # 0, 1, 2, 3, 4
print(f"Epoch {epoch}")
# range(start, stop, step)
for i in range(0, 100, 10): # 0, 10, 20, ..., 90
print(i)
This is a very common beginner confusion!
range() starts at 0 and stops before the end:
β’ range(5) gives 0, 1, 2, 3, 4 β starts at 0, stops before
5
β’ range(1, 5) gives 1, 2, 3, 4 β starts at 1, stops before
5
Lists are also 0-indexed:
β’ my_list[0] is the first item
β’ my_list[1] is the second item
β’ A list with 5 items has indices: 0, 1, 2, 3, 4
Why? This design ensures range(len(items)) always gives valid
indices!
layers = ["embedding", "transformer", "output"]
# Get both index and value
for i, layer in enumerate(layers):
print(f"Layer {i}: {layer}")
# Output:
# Layer 0: embedding
# Layer 1: transformer
# Layer 2: output
# Start counting from 1
for i, layer in enumerate(layers, start=1):
print(f"Layer {i}: {layer}")
predictions = [0.9, 0.3, 0.7]
labels = [1, 0, 1]
# Iterate through pairs
for pred, label in zip(predictions, labels):
correct = (pred > 0.5) == label
print(f"Pred: {pred:.1f}, Label: {label}, Correct: {correct}")
# Output:
# Pred: 0.9, Label: 1, Correct: True
# Pred: 0.3, Label: 0, Correct: True
# Pred: 0.7, Label: 1, Correct: True
Every neural network training uses nested for loops: outer loop for epochs, inner loop for batches. This pattern is universal across frameworks.
# Pseudocode for a typical training loop
num_epochs = 10
batches = [[1,2], [3,4], [5,6]] # Example data batches
for epoch in range(num_epochs):
total_loss = 0
for batch_idx, batch in enumerate(batches):
# Forward pass, compute loss, backward pass
loss = 0.5 # placeholder
total_loss += loss
avg_loss = total_loss / len(batches)
print(f"Epoch {epoch+1}/{num_epochs}, Loss: {avg_loss:.4f}")
While loops repeat as long as a condition is true. They're useful when you don't know in advance how many iterations you need.
# Basic while loop
loss = 1.0
threshold = 0.1
iteration = 0
while loss > threshold:
loss *= 0.9 # Simulate loss decreasing
iteration += 1
print(f"Iteration {iteration}: loss = {loss:.4f}")
print(f"Converged after {iteration} iterations")
Make sure your while condition will eventually become False! Always have a way to exit the loop (condition change or max iterations).
# Always include a maximum iteration limit
loss = 1.0
max_iterations = 1000
iteration = 0
while loss > 0.01 and iteration < max_iterations:
loss *= 0.99
iteration += 1
if iteration == max_iterations:
print("Warning: Max iterations reached")
else:
print(f"Converged in {iteration} iterations")
# Early stopping when validation loss stops improving
losses = [0.5, 0.4, 0.35, 0.36, 0.37, 0.38] # Simulated losses
patience = 2
no_improvement = 0
best_loss = float('inf')
for epoch, loss in enumerate(losses):
if loss < best_loss:
best_loss = loss
no_improvement = 0
print(f"Epoch {epoch}: New best loss {loss}")
else:
no_improvement += 1
print(f"Epoch {epoch}: No improvement ({no_improvement}/{patience})")
if no_improvement >= patience:
print("Early stopping!")
break
# Skip invalid data points
data_points = [0.5, None, 0.8, -0.1, 0.9, None]
for i, value in enumerate(data_points):
if value is None:
print(f"Skipping None at index {i}")
continue
if value < 0:
print(f"Skipping negative value at index {i}")
continue
print(f"Processing: {value}")
Python has a unique feature: an else clause on loops that runs only
if the loop completes normally (not via break).
# Check if any loss exceeds threshold
losses = [0.1, 0.2, 0.15, 0.08]
threshold = 0.5
for loss in losses:
if loss > threshold:
print(f"Found loss above threshold: {loss}")
break
else:
# This runs only if we didn't break
print("All losses are below threshold!")
List comprehensions are a concise way to create lists. They're faster and more "Pythonic" than equivalent for loops.
# Traditional for loop
squares = []
for x in range(5):
squares.append(x ** 2)
# [0, 1, 4, 9, 16]
# List comprehension (same result, one line)
squares = [x ** 2 for x in range(5)]
# [0, 1, 4, 9, 16]
Anatomy of a List Comprehension:
[ expression for item in sequence ]
ββββββββββ βββ ββββ ββ ββββββββ
β β β β
β β β ββ What to iterate over
β β β
β β ββββββββββββ Variable name for each element
β β
β βββββββββββββββββ Keyword "for"
β
ββββββββββββββββββββββββββ What to compute for each item
Example: [x**2 for x in range(5)]
β β
"square x" "for x = 0,1,2,3,4"
Read it like English: "give me x squared, for each x in range(5)"
# Filter: only keep items matching condition
numbers = [1, -2, 3, -4, 5]
positive = [x for x in numbers if x > 0]
# [1, 3, 5]
# Transform + filter
doubled_positive = [x * 2 for x in numbers if x > 0]
# [2, 6, 10]
# Conditional expression (ternary)
abs_values = [x if x >= 0 else -x for x in numbers]
# [1, 2, 3, 4, 5]
Comprehensions are used constantly: [f.read() for f in files] to load data,
[pred > 0.5 for pred in predictions] for thresholding,
[layer for layer in model.layers if 'attention' in layer.name] to filter layers.
# Dictionary comprehension
models = ["bert", "gpt", "t5"]
accuracies = [0.92, 0.95, 0.89]
results = {model: acc for model, acc in zip(models, accuracies)}
# {'bert': 0.92, 'gpt': 0.95, 't5': 0.89}
# Set comprehension (unique values)
labels = [1, 0, 1, 2, 0, 2, 1]
unique_labels = {label for label in labels}
# {0, 1, 2}
accuracy = 0.92
loss = 0.15
if accuracy >= 0.90 and loss <= 0.2:
print("Model ready for deployment!")
else:
print("Model needs more training")
# Output: Model ready for deployment!
loss = 1.0
for epoch in range(1, 6):
loss *= 0.9 # Decrease by 10%
print(f"Epoch {epoch}/5 - Loss: {loss:.4f}")
# Output:
# Epoch 1/5 - Loss: 0.9000
# Epoch 2/5 - Loss: 0.8100
# ...
predictions = [0.2, 0.7, 0.4, 0.9, 0.1]
# Using list comprehension with conditional
binary = [1 if p >= 0.5 else 0 for p in predictions]
print(binary) # [0, 1, 0, 1, 0]
range() for countingzip() for parallel iteration
[x**2 for x in range(5) if x > 0]
Now you can control program flow! Next, learn about functions and data structuresβthe building blocks for organizing larger programs.