Python Loops and List Comprehension

For loops, while loops, enumerate, zip — and the comprehension syntax that replaces about eighty percent of the loops you were about to write.

Python Beginner 18 min read Watch the video (22:35)

This is the longest lesson in the Python track, and the one that changes how your code looks. By the end you will write loops that other Python developers recognise as Python, rather than as C with different punctuation.

#The for loop iterates over things, not counters

If you have used almost any other language, you learned this shape:

javascript
for (let i = 0; i < items.length; i++) {
    console.log(items[i]);
}

Python does not work that way. A for loop walks over the items directly:

python
tracks = ["Python", "PHP", "Ruby", "C#", "Racket"]

for track in tracks:
    print(f"Now teaching: {track}")

No counter, no length check, no index. There is no possible off-by-one error here, because there is no one to be off by.

This works on anything iterable — lists, strings, dictionaries, files, and more:

python
for letter in "Dom":
    print(letter)          # D, o, m

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

#range() when you genuinely need numbers

python
for i in range(5):
    print(i)               # 0 1 2 3 4

for i in range(1, 6):
    print(i)               # 1 2 3 4 5

for i in range(0, 101, 10):
    print(i)               # 0 10 20 ... 100

for i in range(5, 0, -1):
    print(i)               # 5 4 3 2 1

range(start, stop, step) follows the same "stop is excluded" rule as slicing. range(5) gives you five numbers starting at zero — which is exactly the set of valid indexes for a five-item list.

#enumerate() — index and value

python
lessons = ["Intro", "Basics", "Loops", "Regex"]

for index, lesson in enumerate(lessons, start=1):
    print(f"Lesson {index}: {lesson}")
text
Lesson 1: Intro
Lesson 2: Basics
Lesson 3: Loops
Lesson 4: Regex

start=1 is the part people miss. Humans count from one; without it you would be doing index + 1 all over your output strings.

#zip() — walk two lists together

python
names = ["Python", "PHP", "Racket"]
counts = [4, 5, 2]

for name, count in zip(names, counts):
    print(f"{name}: {count} lessons")

zip stops at the shorter list, so there is no index error if the lengths differ. To turn two lists into a dictionary: dict(zip(names, counts)).

#while — loop until a condition changes

Use for when you know what you are iterating over. Use while when you do not know how many times you will go round.

python
total = 0

while True:
    entry = input("Score (or 'done'): ")
    if entry == "done":
        break
    total += int(entry)

print(f"Total: {total}")

while True with a break is the standard shape for "keep asking until they tell me to stop". It is clearer than trying to cram the exit condition into the while line.

  • break exits the loop immediately.
  • continue skips the rest of this iteration and goes to the next one.
python
for n in range(1, 21):
    if n % 3 != 0:
        continue          # not a multiple of 3, skip it
    if n > 15:
        break             # stop entirely
    print(n)              # 3 6 9 12 15

#Comprehensions — the part that makes code look like Python

Here is a loop that builds a new list:

python
squares = []
for n in range(1, 11):
    squares.append(n ** 2)

Four lines, and three of them are ceremony. Here is the same thing:

python
squares = [n ** 2 for n in range(1, 11)]

That is a list comprehension. Read it left to right: the square of n, for each n in that range.

The shape is always:

python
[EXPRESSION for ITEM in ITERABLE if CONDITION]

The if is optional and filters:

python
evens = [n for n in range(20) if n % 2 == 0]

titles = [v["title"] for v in catalogue if v["views"] > 200]

names = [name.strip().title() for name in raw_names if name.strip()]

That last line does three jobs at once: drop the blank entries, trim the whitespace, and title-case what is left. Written as a loop it is six lines and a nested if.

#Dictionary and set comprehensions

Same idea, different brackets:

python
lengths = {word: len(word) for word in ["python", "php", "ruby"]}
# {'python': 6, 'php': 3, 'ruby': 4}

unique_tracks = {v["track"] for v in catalogue}
# {'python', 'php', 'racket'}

#When not to use one

Also: if the body is doing something rather than producing something (printing, writing files, calling an API), use a normal for loop. Building a list of Nones just to throw it away is a misuse of the syntax.

#Generator expressions — comprehensions that do not build the list

Swap the square brackets for round ones and you get a generator: it produces items one at a time instead of building the whole list in memory.

python
total = sum(v["views"] for v in catalogue)

if any(v["views"] > 500 for v in catalogue):
    print("We have a hit.")

if all(v["duration"] for v in catalogue):
    print("Every video has a duration.")

For a list of five that hardly matters. For a ten-million-line log file it is the difference between running and running out of RAM. When you are feeding a comprehension straight into sum, any, all, min or max, drop the brackets.

#Nested loops

python
for track in tracks:
    print(f"\n{track}")
    for lesson in lessons_for(track):
        print(f"  - {lesson}")

Each level of nesting multiplies the work. Two nested loops over a thousand items each is a million iterations — noticeable. Three is a billion — not viable. When you find yourself nesting to search for matches, ask whether a set or dict could do the lookup instead. That instinct is most of what interview performance rewards.

#Put it together

python
catalogue = [
    {"title": "Intro to Python", "views": 197, "track": "python", "seconds": 647},
    {"title": "Python Basics", "views": 191, "track": "python", "seconds": 898},
    {"title": "Python Loops", "views": 381, "track": "python", "seconds": 1355},
    {"title": "PHP Web Form", "views": 259, "track": "php", "seconds": 266},
    {"title": "Racket Install", "views": 696, "track": "racket", "seconds": 234},
]

# Group titles by track — no imports needed
by_track = {}
for video in catalogue:
    by_track.setdefault(video["track"], []).append(video["title"])

for track, titles in by_track.items():
    total = sum(v["seconds"] for v in catalogue if v["track"] == track)
    minutes = total // 60
    print(f"\n{track.upper()} — {len(titles)} lessons, {minutes} minutes")
    for i, title in enumerate(titles, start=1):
        print(f"  {i}. {title}")

popular = [v["title"] for v in catalogue if v["views"] > 250]
print(f"\nOver 250 views: {', '.join(popular)}")
text
PYTHON — 3 lessons, 48 minutes
  1. Intro to Python
  2. Python Basics
  3. Python Loops

PHP — 1 lessons, 4 minutes
  1. PHP Web Form

RACKET — 1 lessons, 3 minutes
  1. Racket Install

Over 250 views: Python Loops, PHP Web Form, Racket Install

dict.setdefault(key, []) returns the existing list for that key, or inserts a fresh empty list and returns that. It is the neat way to group without checking whether the key exists first.

Check yourself

3 questions on what you just read. No score is kept — it is only here to catch the bits that did not stick.

  1. 1You need both the position and the value while looping. What is the idiomatic way?

    Show the answer

    A. for i, item in enumerate(items, start=1):

    enumerate gives you both without an index dance, and start=1 saves you writing i + 1 all over your output.

  2. 2When should you use a generator expression instead of a list comprehension?

    Show the answer

    A. When the result feeds straight into sum, any, all, min or max.

    Round brackets produce items one at a time instead of building the whole list. On a huge file that is the difference between running and running out of memory.

  3. 3Which of these is the wrong job for a comprehension?

    Show the answer

    A. Printing each item, or writing them to a file.

    Comprehensions are for producing a collection. If the body does something rather than making something, use a plain for loop — otherwise you build a list of Nones and throw it away.

#Key takeaways

  • Loop over items, not indexes. Use enumerate when you need the position too.
  • range(stop) excludes the stop value, matching how slices work.
  • while True plus break is the idiomatic "until the user stops" loop.
  • Comprehensions replace build-a-list loops — but only while they stay readable in one breath.
  • Round brackets make a generator: no intermediate list, no memory spike.

Next: regular expressions, for when the thing you are looking for has a shape rather than a fixed value.

Finished this one?

The challenge above has no posted solution — that is deliberate. Get it working, then come back for the next lesson.

Next lesson Subscribe