Jako Heiberg
HomeInsightsMusingsPortfolioAboutContact
Jako Heiberg

From DBase III+ to edge computing.
The journey continues.

Pages

  • About
  • Portfolio
  • Resume / CV
  • Insights
  • Musings
  • Archive
  • Contact

© 2026 Jako Heiberg · Cape Town, South Africa · UTC+2

Built with React + Cloudflare Workers. No PHP was harmed.

Back to Insights
Insights

Python From Scratch — Part 4: Loops

You told Python to remove every 2 from the list, and one survived. Loops are the friendliest thing in the language until they quietly skip an item or run until the heat death of the universe — here's every loop you need, and the two traps that get everyone once.

October 14, 2026
8 min read
Python

Part 4 of 23 · Level: Beginner · From Hello World to Neural Networks

Sooner or later every program has to do the same thing more than once, and copy-pasting a line forty times is how you end up with a bug in thirty-nine of them. Loops let you write the work once and tell Python how often to run it. If Part 3 taught you how a program decides whether to do something, this part is about doing it again and again without losing your mind.

The for loop: walking over things

The for loop takes anything iterable — a list, a string, a tuple, a dictionary, even a file — and hands you one item per pass. No counter to increment, no index to babysit.

colours = ["red", "green", "blue"]
for colour in colours:
    print(colour.upper())

Output:

RED
GREEN
BLUE

colour is just a name you pick; Python reassigns it to the next item each time round. Strings are iterable too, so you can loop over characters the same way:

for letter in "hi":
    print(letter)

Output:

h
i

The vast majority of loops you'll ever write look exactly like the first example: hand for a collection, do something with each item. That's the whole idea.

Counting with range()

Sometimes you don't have a list — you just want to do something a fixed number of times. That's what range() is for.

for i in range(3):
    print(f"attempt {i}")

Output:

attempt 0
attempt 1
attempt 2

Two things to internalise now and save yourself pain later: range starts at 0, and it stops before the number you give it. range(3) yields 0, 1, 2 — three values, no 3. That half-open behaviour feels odd for about a week and then feels obviously correct forever.

range also takes a start, a stop, and a step:

for n in range(2, 11, 2):
    print(n)

Output:

2
4
6
8
10

Start at 2, stop before 11, step by 2. Note that 11 never prints — the stop is always exclusive. A negative step counts down: range(3, 0, -1) gives you 3, 2, 1.

When you need the index too: enumerate()

New Python programmers reach for range(len(colours)) to get positions. It works, but it's the loop equivalent of holding your fork with both hands. When you need the item and its index, use enumerate():

names = ["Ada", "Alan", "Grace"]
for i, name in enumerate(names, start=1):
    print(f"{i}. {name}")

Output:

1. Ada
2. Alan
3. Grace

enumerate hands back a (index, item) pair each pass, and Python unpacks it into i and name for you. The start=1 is optional — handy when you're numbering things for humans, who stubbornly begin counting at one.

Looping over a dictionary

Loop over a dict and you get its keys by default:

scores = {"Ada": 90, "Alan": 85}
for name in scores:
    print(name)

Output:

Ada
Alan

When you want keys and values together, use .items() and unpack the pair:

for name, score in scores.items():
    print(f"{name} scored {score}")

Output:

Ada scored 90
Alan scored 85

There's also .values() for when you only care about the values. That's enough dict-looping for now — we'll go deeper when dictionaries get their own part.

The while loop: when you don't know how many times

for is for "loop over these." while is for "keep going until." It runs as long as its condition is true — and the condition follows the exact truthiness rules from Part 3.

countdown = 3
while countdown > 0:
    print(countdown)
    countdown -= 1
print("liftoff")

Output:

3
2
1
liftoff

The crucial detail: something inside the loop has to eventually make the condition false. Here it's countdown -= 1 (shorthand for countdown = countdown - 1). Forget that line and you've built a machine that counts to three forever. We'll come back to that.

break and continue

Two words give you fine control inside any loop. break bails out of the loop entirely. continue skips the rest of the current pass and jumps to the next one.

for n in range(1, 10):
    if n == 5:
        break
    print(n)

Output:

1
2
3
4

The loop was set up to run to 9, but break yanked it out the moment n hit 5. Now continue, to skip the even numbers:

for n in range(1, 7):
    if n % 2 == 0:
        continue
    print(n)

Output:

1
3
5

% is the remainder operator, so n % 2 == 0 means "n is even." When it's even, continue skips straight past the print.

Nested loops

A loop can live inside another loop. The inner one runs all the way through for every single pass of the outer one — which is how you produce a grid, like a times table:

for row in range(1, 4):
    for col in range(1, 4):
        print(row * col, end=" ")
    print()

Output:

1 2 3
2 4 6
3 6 9

(end=" " tells print to end with a space instead of a newline, so a row stays on one line; the bare print() afterwards drops to the next line.) One thing to know up front: break only escapes the innermost loop it's in, not all of them. And if you find yourself three loops deep, that's usually a nudge to pull some of it into a function or rethink your data.

for ... else: the clause nobody remembers exists

Here's a genuine Python oddity. A for (or while) loop can have an else block, and it runs only if the loop finished without hitting a break. It's tailor-made for "search through these, and tell me if nothing matched."

haystack = [3, 7, 11, 15]
for n in haystack:
    if n % 2 == 0:
        print("found an even number")
        break
else:
    print("no evens here")

Output:

no evens here

The trick to reading it: mentally rename else to "no break." The loop ran to completion, never broke, so the else fired. If the list had contained an even number, we'd have printed and broken out — and the else would have been skipped. Almost nobody remembers this feature exists, which makes it a small delight when it saves you a flag variable.

Gotcha: the accidental infinite loop

The most common while bug in the world is forgetting to change the thing the condition checks:

# DON'T run this — it never stops
n = 5
while n > 0:
    print(n)
    # forgot to write n -= 1, so n stays 5 forever

n never drops below 1, the condition stays true, and your terminal fills with 5s until the heat death of the universe (or you press Ctrl+C, which interrupts a runaway program). Whenever you write a while, immediately ask: what in here makes the condition eventually false?

Gotcha: don't delete from a list while looping over it

This one burns everybody exactly once. Say you want to strip every 2 out of a list:

nums = [1, 2, 2, 3]
for n in nums:
    if n == 2:
        nums.remove(2)
print(nums)

Output:

[1, 2, 3]

You asked it to remove every 2, and one survived. Here's why: the loop tracks its position by index. When you remove the first 2, everything after it slides one slot to the left — so the second 2 shifts into a position the loop has already walked past. It gets skipped.

The fix is to never mutate the thing you're iterating. Loop over a copy, or better, build a fresh list of what you want to keep:

nums = [1, 2, 2, 3]
nums = [n for n in nums if n != 2]
print(nums)

Output:

[1, 3]

That bracket syntax is a list comprehension — a compact "build a new list from an old one" — and it gets its own spotlight later in the series. For now, just know the rule: read the list you're looping, write to a new one.

Next up

You can now make Python repeat itself intelligently. In Part 5: Functions we package a chunk of that logic behind a name you can call again and again — turning "loops that do a thing" into "a thing you can reuse anywhere," without the copy-paste that started all this trouble.

Jako Heiberg

Software developer with 40+ years of building things that work. Full-stack, FastAPI, React. Based in Cape Town, working remotely, worldwide.

Related Posts

Insights

Python From Scratch — Part 3: Booleans & Control Flow

Every `if` you write rests on a value that's either `True` or `False` — but `==` and `is` are not the same question, and the day you confuse them on the number 257 is the day you find out why. Booleans, truthiness, and Python's whitespace-is-the-syntax rule, up close.

August 12, 2026
8 min read
Insights

Python From Scratch — Part 2: Variables, Numbers & Strings

`input()` hands you back the string "25", never the number 25 -- and that one fact is behind more first-week Python bugs than anything else. This part covers variables, numbers and strings, plus the type-conversion trick that stops `"3" + 3` from ruining your afternoon.

July 28, 2026
9 min read
Insights

Python From Scratch — Part 1: Hello, World

Getting Python installed and running is the part that quietly stops more beginners than loops, classes, or neural networks ever will. Part 1 takes you from nothing installed — on Windows, macOS, or Linux — to your first running program, with a warning about the apostrophe that bites on line one.

July 23, 2026
8 min read