Chapter 7 of 24 · Python for Beginners — Complete Free Tutorial

Loops and Iteration

Everything you have written in the Python for Beginners series so far has run from top to bottom exactly once. Real programs repeat work: print every row, retry until the input is valid, total a column of numbers. Python gives you two loops for that — for when you know what you are walking over, and while when you do not — plus three steering commands: break, continue and the loop else clause. You will also meet enumerate() and zip(), the two built-ins that remove almost all index juggling, and a first glimpse of the comprehension syntax that chapter 11 takes apart properly.

for loops over a sequence

A for loop asks Python to hand it one item at a time from any iterable — a list, a string, a range, a dictionary. There is no index variable to keep in step, so there is no off-by-one error to make:

fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

Expected output:

apple
banana
cherry

Each pass binds fruit to the next item and runs the indented block; when the sequence runs out, the loop ends and the program continues underneath it. The indentation is the block, exactly as it was for if in Control Flow and Conditionals, and the loop variable keeps the value of the last item once the loop finishes.

Counting with range()

When you need numbers rather than items, loop over range(). It produces an arithmetic sequence that stops before its second argument, which is why a loop that “should” run four times sometimes runs three:

for n in range(1, 4):
    print(n)
print(list(range(0, 10, 2)))

Expected output:

1
2
3
[0, 2, 4, 6, 8]

range(4) counts 0, 1, 2, 3 from zero, and the third argument is the step (negative steps count backwards). If you catch yourself writing for i in range(len(items)) only to read items[i] on the next line, that is the signal to loop over the items directly, or to reach for enumerate() later in this chapter.

while: repeat until the condition changes

A while loop repeats as long as a condition stays true. It is the right tool when the number of passes depends on data — waiting for a usable password, draining a queue, playing a guessing game. You own the condition, so you also own whatever moves it towards false: delete countdown -= 1 below and you have written an infinite loop that spins until you press Ctrl+C.

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

Expected output:

3
2
1
Liftoff!

break, continue and the loop else clause

Three commands steer a loop mid-flight. break leaves the loop immediately and the code after it runs as usual; continue skips the rest of this pass and starts the next one; and the unusual one — a loop may carry an else clause that runs only when the loop finished without a break. Watch the first two here:

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

Expected output:

1
3
5
7

The else on a loop is the one piece of syntax here with no JavaScript equivalent, and it is easiest to remember as “nobody bailed out”. It reads as “search for a divisor, and if none is found, call the number prime”:

for candidate in (4, 9, 7):
    for divisor in range(2, candidate):
        if candidate % divisor == 0:
            print(candidate, "is composite")
            break
    else:
        print(candidate, "is prime")

Expected output:

4 is composite
9 is composite
7 is prime

Note that the else lines up with the inner for, not with the if: it reports that the divisor search ran to the end without breaking. The loop family itself is close to JavaScript's for…of and while, covered side by side in the JavaScript series.

enumerate() and zip()

Two built-ins remove the index juggling. enumerate() hands you the position and the item together, and zip() walks two sequences in step, pairing them up element by element:

names = ["Ada", "Grace", "Alan"]
scores = [95, 91, 88]
for position, (name, score) in enumerate(zip(names, scores), start=1):
    print(f"{position}. {name}: {score}")

Expected output:

1. Ada: 95
2. Grace: 91
3. Alan: 88

Both built-ins unpack automatically: zip() pairs the two sequences element by element, and enumerate() wraps each pair in a position — which is also a preview of the tuple unpacking chapter 8 covers. zip() stops at the shorter sequence, so unequal lengths silently drop the tails; check the lengths first when that matters. Readers who know array methods will recognise the shape immediately; Python simply gives these two jobs their own built-ins instead of methods on the array.

Nested loops, and lists you change while looping

Loops nest: the divisor search you just read runs an inner loop to completion for every pass of the outer one, which is how grids, tables and pairwise comparisons are written. The other pattern worth knowing is what not to do — changing a list while the loop is still walking over it. Deleting an item shifts every later position left, so the iterator skips its neighbour:

scores = [10, 45, 72, 33, 88]
for score in scores:
    if score < 60:
        scores.remove(score)
print(scores)

Expected output:

[45, 72, 88]

45 should be gone as well, but removing 10 slid it into the slot the iterator had just left behind. Build a fresh list instead — passed = [s for s in [10, 45, 72, 33, 88] if s >= 60] answers [72, 88] and never touches the source, a list comprehension that chapter 11 covers properly.

A worked program: running totals

Loops become useful when they carry state from one pass to the next. This program walks a list of prices, prints a running total, and reports the final figure:

prices = [4.99, 12.5, 3.25]
total = 0
for i, price in enumerate(prices, start=1):
    total += price
    print(f"item {i}: ${price:.2f}  running total: ${total:.2f}")
print(f"{len(prices)} items, final total: ${total:.2f}")

Expected output:

item 1: $4.99  running total: $4.99
item 2: $12.50  running total: $17.49
item 3: $3.25  running total: $20.74
3 items, final total: $20.74

Three ideas in a few lines: total survives between passes, the {price:.2f} format spec pins the display to two decimals, and the loop reports as it goes. When a running total is not needed, most loops like this collapse into a single expression — that is exactly what the next few chapters build towards.

Common mistakes

  • Expecting range(1, 4) to include 4 — the stop value is exclusive; write range(1, 5) for four passes.
  • Confusing = with == — one stores a value, the other asks a question; a condition written with a single = fails with a SyntaxError, which is better than a loop that never notices anything changed.
  • Forgetting to move the condition — a while whose variable never changes loops forever; Ctrl+C stops the program, but the missing update is the real bug.
  • Deleting from a list inside its own loop — positions shift and neighbours get skipped; filter into a new list instead of editing the one you are reading.
  • Treating loop else like if…else — it is not the opposite of the loop body; it runs only when the loop ended without a break.

Practice checklist

  1. Print every character of a string with its position using enumerate(), starting the count at 1.
  2. Write a while loop that keeps asking for a number until it receives a negative one, then interrupt it once on purpose with Ctrl+C.
  3. Merge two equal-length lists with zip() and print one formatted line per pair.
  4. Reproduce the skipping bug above, then fix it with a comprehension and confirm the result matches your expectation.
  5. Extend the running-total program to print the average price after the loop, and check the figure with a calculator.

What's next?

You can repeat work and walk over data — now you need the containers that hold it. Chapter 8 introduces ordered collections and their immutable cousins in Lists and Tuples.

Quiz: Loops and Iteration

5 questions · 70% to pass

Frequently Asked Questions

Python is a general-purpose language first released in 1991, designed around one idea: code is read far more often than it is written. English keywords and indentation instead of braces make it readable, the standard library covers files, JSON, dates and testing, and pip adds thousands more packages. The same language handles scripts, web APIs, data work and machine learning.

None at all. Python is widely considered the friendliest first language: no declaration keywords, no braces, no build step, and error messages that name the problem. This series begins at your first print() call. If you already read JavaScript or TypeScript you will adjust quickly, but no previous language is assumed anywhere in these 24 chapters.

Python 3 only, any release from 3.12 onwards. Python 2 reached end of life in 2021 and its print "hi" statements and xrange() no longer run. One historical quirk remains: installers ship the interpreter as python3 on macOS and Linux, while Windows usually offers python or the py launcher, and every command in this series works with that one-word swap.

More than you might expect. Everyday automation such as renaming files, cleaning spreadsheets or scraping pages; web backends with Django, Flask or FastAPI; data analysis and machine learning with pandas, NumPy and PyTorch; DevOps tooling, desktop apps and games. Python is also the classic glue language, calling C, Rust or Fortran libraries from short readable scripts.

Pure Python can run 10 to 100 times slower than C, yet that rarely blocks you: heavy numeric work lives in C extensions such as NumPy, which Python coordinates. Dynamic typing does push errors to runtime, surfacing as a TypeError on the line that finally uses the bad value. Type hints checked by mypy move those mistakes back into your editor.

Only the interpreter. Install CPython from python.org, version 3.12 or newer, confirm it with python3 --version, and open a plain text editor; VS Code with the Python extension is the recommendation here. The early chapters use no third-party packages, so there is nothing else to set up until virtual environments and pip arrive.

A dict is a real runtime mapping: keys may be any hashable value rather than only strings, insertion order has been guaranteed since Python 3.7, and a missing key raises KeyError unless you ask with get(). JavaScript objects coerce every key to a string and carry a prototype chain. A TypeScript interface is erased at compile time and describes shape only; TypedDict is Python's checked equivalent.

Reach for a list when items will be added, removed or reordered: it is ordered, mutable and rich with methods. Pick a tuple for fixed records and function returns, because it is immutable and hashable, so it can be a dictionary key. Choose a set for unique values and fast membership checks; it has no order and de-duplicates on creation.

Yes, gradually. Annotate public function signatures first; the interpreter ignores hints at runtime, so a checker such as mypy is what gives them teeth. Add tests the same way: pytest collects any test_*.py file and plain assert statements, and a suite that finishes in a tenth of a second is one you will run after every change. Both habits cost little now and much more later.

JavaScript and TypeScript keep the browser, so anything rendered to the user stays theirs. Python takes the server: Django for full-featured applications, Flask for small ones, FastAPI for typed async APIs where your annotations become automatic documentation. Your TypeScript habits transfer directly, and asyncio mirrors the event loop you already know, just with different keywords.