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.
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:
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:
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:
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
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 1range(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
lessons = ["Intro", "Basics", "Loops", "Regex"]
for index, lesson in enumerate(lessons, start=1):
print(f"Lesson {index}: {lesson}")Lesson 1: Intro
Lesson 2: Basics
Lesson 3: Loops
Lesson 4: Regexstart=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
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.
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.
breakexits the loop immediately.continueskips the rest of this iteration and goes to the next one.
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:
squares = []
for n in range(1, 11):
squares.append(n ** 2)Four lines, and three of them are ceremony. Here is the same thing:
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:
[EXPRESSION for ITEM in ITERABLE if CONDITION]The if is optional and filters:
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:
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.
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
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
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)}")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 Installdict.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.
-
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):enumerategives you both without an index dance, andstart=1saves you writingi + 1all over your output. -
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,minormax.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.
-
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
forloop — otherwise you build a list ofNones and throw it away.
#Key takeaways
- Loop over items, not indexes. Use
enumeratewhen you need the position too. range(stop)excludes the stop value, matching how slices work.while Trueplusbreakis 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.