Writing Logic and Functions

Loops: Repeating Actions with for and while

8 min read

Some tasks require repetition

Imagine you need to send a "Happy Birthday" email to each of 500 customers. Writing 500 separate print() calls is absurd. What you need is a way to repeat the same action for each item in a list — automatically.

That's exactly what loops do. A loop runs a block of code repeatedly, either once for each item in a collection (for loop) or as long as a condition is true (while loop).


The for loop

A for loop goes through each item in a collection — one at a time — and runs the code block for each one.

colors = ["red", "green", "blue"]
for color in colors:
    print("Color:", color)

Output:

Color: red
Color: green
Color: blue

Reading it aloud: "For each color in colors, print 'Color:' followed by that color."

Python picks the next item from the list on each pass through the loop, assigns it to color, and runs the indented block. When the list runs out, the loop ends.


Looping over a range of numbers

range() generates a sequence of numbers — useful when you want to repeat something a specific number of times:

for i in range(5):
    print("Step:", i)

Output:

Step: 0
Step: 1
Step: 2
Step: 3
Step: 4

range(5) produces the numbers 0, 1, 2, 3, 4 (not including 5). range(1, 6) would produce 1, 2, 3, 4, 5.

range() also accepts an optional step as a third argument:

print(list(range(4, 10)))     # [4, 5, 6, 7, 8, 9]
print(list(range(4, 10, 2)))  # [4, 6, 8]

range(start, end) counts from start up to (but excluding) end. Adding a third number, range(start, end, step), changes how much it counts by each time.


Looping with an index: enumerate()

Sometimes you need both an item and its position while looping. enumerate() gives you both at once:

colors = ["red", "green", "yellow", "brown"]
for index, color in enumerate(colors):
    print(f"color {color} at pos {index}")

Output:

color red at pos 0
color green at pos 1
color yellow at pos 2
color brown at pos 3

Without enumerate(), getting the index would mean looping over range(len(colors)) and looking up colors[i] manually — more code, more room for off-by-one mistakes.

The Pythonic way to loop

There's more than one way to loop over a list by index, but they're not equally good:

# Works, but not idiomatic Python
i = 0
while i < len(my_list):
    do_something(my_list[i])
    i += 1

# Better — no manual counter to manage
for i in range(len(my_list)):
    do_something(my_list[i])

# Best — when you don't actually need the index
for element in my_list:
    do_something(element)

Prefer the plainest loop that gets the job done: iterate directly over the collection unless you specifically need the index, and reach for enumerate() when you need both the index and the value.


The while loop

A while loop keeps running as long as a condition is True. It checks the condition before each iteration.

countdown = 5
while countdown > 0:
    print("Countdown:", countdown)
    countdown -= 1
print("Blast off!")

Output:

Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Blast off!

Step by step:

  1. Check: is countdown > 0? Yes (5 > 0) → run the block
  2. Print Countdown: 5, then subtract 1 → countdown is now 4
  3. Check again: is 4 > 0? Yes → run again
  4. ... repeat until countdown reaches 0
  5. Check: is 0 > 0? No → loop ends, program continues

Warning: if the condition never becomes False, the loop runs forever — an "infinite loop." Always make sure your while loop has a way to end.


Diagram showing two flowcharts side by side: the for loop (items list → pick next item → run block → any items left? → yes loops back, no exits) and the while loop (check condition → True runs block → False exits)


Combining loops and conditionals

Loops and if statements work extremely well together. Here's an example that categorizes a list of numbers:

numbers = [3, 10, 7, 2, 11, 8]
for num in numbers:
    if num % 2 == 0:
        print(num, "is even")
    else:
        print(num, "is odd")

Output:

3 is odd
10 is even
7 is odd
2 is even
11 is odd
8 is even

The % operator gives the remainder after division. A number is even if the remainder after dividing by 2 is 0.


break and continue

Two special keywords give you extra control inside loops:

break — exit the loop immediately

numbers = [5, 3, 8, 1, 9, 2]
for num in numbers:
    if num == 8:
        print("Found 8! Stopping.")
        break
    print(num)

Output:

5
3
Found 8! Stopping.

continue — skip the rest of the current iteration

for i in range(1, 8):
    if i == 4:
        continue  # skip 4
    print(i)

Output:

1
2
3
5
6
7

The pass statement

pass is a null operation — a placeholder that does nothing when it runs. Python requires every if, for, while, and def block to contain at least one statement, so pass is what you use when you don't have any code to write yet, but the syntax still needs a body:

for i in range(10):
    pass   # loop body intentionally left empty for now

def todo_later():
    pass   # function stub — implement this later

It's most useful while you're sketching out a program's structure and want to fill in the logic afterward without breaking the syntax in the meantime.


The for...else construct

A for loop can have an else clause. It runs once, automatically, only if the loop finishes without hitting a break. This is useful for search-style loops where you want to know whether something was found.

for num in range(2, 50):
    for divisor in range(2, num):
        if num % divisor == 0:
            print(num, "Not Prime")
            break
    else:
        print(num, "Prime")

For each num, the inner loop checks every possible divisor. If it finds one that divides evenly, it prints "Not Prime" and breaks immediately — which skips the inner loop's else. If the inner loop never finds a divisor and runs to completion, its else clause fires and prints "Prime".

Read for...else as: "else, if the loop never broke." It pairs naturally with break — without a break in the loop, else would just run every time, which isn't very useful.


Useful patterns

Sum all items in a list

sales = [120, 85, 200, 67, 145]
total = 0
for amount in sales:
    total = total + amount
print("Total sales:", total)  # 617

Collect items that match a condition

scores = [45, 82, 61, 90, 38, 74]
passing = []
for score in scores:
    if score >= 60:
        passing.append(score)
print("Passing scores:", passing)
# [82, 61, 90, 74]

Loop through a dictionary

inventory = {
    "pen": 12,
    "notebook": 5,
    "eraser": 3
}
for item, count in inventory.items():
    print(f"{item}: {count} in stock")

Output:

pen: 12 in stock
notebook: 5 in stock
eraser: 3 in stock

for vs. while — when to use each

Use for when...Use while when...
You're iterating over a known collectionYou're running until a condition changes
You know exactly how many times to repeatYou don't know in advance how many times to repeat
Processing a list, string, or dictionaryWaiting for user input, polling a status, counting down

Most loops in Python are for loops. Reach for while when you genuinely don't know how many iterations you'll need.


Key takeaway

for loops repeat code once for each item in a collection. while loops repeat code as long as a condition is true. Both can be combined with if statements to add conditional logic inside the repetition. break exits a loop early; continue skips the current iteration. Loops are one of the most powerful tools in programming — almost nothing useful gets done without them.

What's next?

As programs grow, you'll find yourself writing the same logic in multiple places. Functions solve this by letting you package reusable code under a name — write it once, call it anywhere.