Unit 2 · Data & Control Flow

Lesson · Unit 2 · 9 min read

For loops & range, the workhorse you'll write in every script.

Most loops you'll ever write are for loops. They walk through any iterable — a list, a string, a dict, the lines of a file — one item at a time. Here's the shape, the helpers that make them shorter, and the comprehension form for one-liners.

Section · 01

The basic shape

A forloop says “for each item in this collection, run this block.” The iteration variable takes a new value each time around:

orders = [42.50, 89.00, 17.95, 230.00]

for amount in orders:
    print(f"Charging ${amount:.2f}")

Same shape works on anything that’s “iterable” — strings, tuples, sets, dicts, file handles, even custom objects you define yourself later:

# string: iterates characters
for ch in "Python":
    print(ch)

# dict: iterates keys
for key in {"a": 1, "b": 2}:
    print(key)

# range: iterates numbers (more on this below)
for i in range(3):
    print(i)

Section · 02

range — generate sequences of numbers

Most of the time you want “do this N times” or “count from A to B.” That’s what range is for. Three forms:

range(5)           # 0, 1, 2, 3, 4         — stop only
range(2, 6)        # 2, 3, 4, 5            — start, stop
range(0, 20, 5)    # 0, 5, 10, 15          — start, stop, step
range(10, 0, -1)   # 10, 9, 8, ..., 1      — counting down (negative step)

Three things to remember about range:

1. The stop value is EXCLUSIVE. range(5) goes 0..4, not 0..5.
2. range() doesn't build a list — it generates numbers on demand.
   That's why range(1_000_000_000) is fast and doesn't eat your RAM.
3. To get a real list from a range, wrap it: list(range(5)) → [0, 1, 2, 3, 4]

Section · 03

enumerate — when you need the index too

Sometimes you want both the position and the value. Beginners write this:

# DON'T do this:
for i in range(len(orders)):
    print(f"{i}: ${orders[i]:.2f}")

It works, but it’s un-Pythonic and slower than it looks. Reach for enumerate instead:

for i, amount in enumerate(orders):
    print(f"{i}: ${amount:.2f}")

# 0: $42.50
# 1: $89.00
# 2: $17.95
# 3: $230.00

Pass start=1 if you want 1-based numbering for display:

for n, amount in enumerate(orders, start=1):
    print(f"Order #{n}: ${amount:.2f}")

Section · 04

zip — walk two collections in parallel

When you have two related lists, pair them up with zip:

names  = ["Ada", "Ben", "Cal"]
scores = [92, 71, 84]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

# Ada: 92
# Ben: 71
# Cal: 84

zipstops at the shortest list, so you don’t have to check lengths. It also works on three or more iterables at once. Pair it with dict() to build a dict from two parallel lists:

scoreboard = dict(zip(names, scores))
# {"Ada": 92, "Ben": 71, "Cal": 84}

Section · 05

Looping over dicts

A bare for x in some_dictgives you keys. Most of the time you want both keys and values — that’s .items():

prefs = {"theme": "dark", "language": "en", "page_size": 25}

for key in prefs:                  # just keys
    print(key)

for value in prefs.values():       # just values
    print(value)

for key, value in prefs.items():   # both — the most common form
    print(f"{key} = {value}")

Unpacking the (key, value) tuple in the loop header is exactly the same move as unpacking from zip or enumerate— Python has one consistent pattern for “give each item a name as I loop over it.”

Section · 06

List comprehensions — for loops in one line

Two patterns show up so often they deserve a shortcut: building a new list by transforming an old one, and filtering a list. Python gives you a one-line form for both, called a list comprehension:

# Transform: list of squares
numbers = [1, 2, 3, 4, 5]

# Long form:
squares = []
for n in numbers:
    squares.append(n * n)

# Comprehension:
squares = [n * n for n in numbers]
# [1, 4, 9, 16, 25]

# Filter: only even numbers
evens = [n for n in numbers if n % 2 == 0]
# [2, 4]

# Combined transform + filter
even_squares = [n * n for n in numbers if n % 2 == 0]
# [4, 16]

The shape is always [expression for item in iterable if condition]. The if is optional.

Comprehensions are great when they stay simple. The moment they get nested or grow a multi-clause condition, write the boring for-loop version — it’ll read better. The skill is knowing when each is the right call, and that comes with practice.

Same idea for dicts and sets

# Dict comprehension
scoreboard = {name: score for name, score in zip(names, scores)}

# Set comprehension
unique_lengths = {len(word) for word in ["python", "for", "everybody"]}
# {3, 6, 9}

Curriculum source

Lesson content is original to YorkSims. Topic structure aligns with Python for Everybody by Dr. Charles R. Severance (py4e.com), licensed under Creative Commons Attribution 3.0 Unported.