Python Loops and Iteration: The Complete Beginner's Guide
Conditionals choose a path; loops repeat a path. This chapter covers \`for\` and \`while\`, practical use of \`range()\`, \`enumerate()\`, and \`zip()\`, plus break/continue, loop else, and nested-loop patterns for real collections.
Chapter 9 of 20 · Beginner · 45 min · Python Programming Course
In Day 8 you learned how to make programs choose a path. Now you will learn how to make programs repeat a path — automatically, reliably, and without writing the same code multiple times.
Loops are what turn a 5-line script into a program that processes 50,000 records. The same code that prints one name can print ten thousand names. The same logic that validates one form can validate every form in a database. That scalability — doing something once and having Python repeat it as many times as needed — is one of the most powerful ideas in all of programming.
Python has two loop types. The for loop iterates over a sequence of items — a list, a string, a range of numbers, a dictionary. The while loop repeats as long as a condition remains True. Together they handle every repetition pattern you will encounter.
What You Will Learn in This Chapter
By the end of this tutorial you will be able to:
- Write
forloops to iterate over lists, strings, tuples, and dictionaries - Use
range()to generate number sequences with full control - Use
enumerate()for index-aware iteration - Use
zip()to iterate over multiple sequences simultaneously - Write
whileloops with correct termination conditions - Control loop execution with
break,continue, andpass - Use
elseclauses on loops - Write nested loops for 2D data
- Use loop patterns: accumulation, searching, filtering, transformation
- Avoid infinite loops and other common loop mistakes
Estimated time: 45 minutes reading + 25 minutes practice
The for Loop — Iterating Over a Sequence
A for loop takes each item from a sequence one at a time and runs the loop body with that item. When the sequence is exhausted, the loop ends:
fruits = ["apple", "banana", "cherry", "mango"]
for fruit in fruits:
print(fruit)
Output:
apple
banana
cherry
mango
The variable fruit is created by the loop — you name it whatever makes sense for the data. Each iteration Python assigns the next item to that variable and runs the indented block. When the list runs out, execution continues after the loop.
# The loop variable name is your choice
for name in ["Alice", "Bob", "Carol"]:
print(f"Hello, {name}!")
for number in [10, 20, 30, 40]:
print(number * 2)
for character in "Python":
print(character)
Output of the last loop:
P
y
t
h
o
n
Strings are sequences of characters — for loops over them character by character. The same loop syntax works on any sequence: lists, tuples, strings, sets, dictionaries, and anything else Python considers iterable.
range() — Generating Number Sequences
range() generates a sequence of integers without creating them all in memory at once. It is the standard way to loop a specific number of times or to work with numeric indexes.
range(stop) — From 0 to stop-1
for i in range(5):
print(i)
# 0, 1, 2, 3, 4
range(5) generates 0, 1, 2, 3, 4 — five numbers starting from 0. The stop value is always excluded.
range(start, stop) — From start to stop-1
for i in range(1, 6):
print(i)
# 1, 2, 3, 4, 5
for i in range(5, 10):
print(i)
# 5, 6, 7, 8, 9
range(start, stop, step) — With a Step Size
# Count by 2s
for i in range(0, 11, 2):
print(i)
# 0, 2, 4, 6, 8, 10
# Count backwards
for i in range(10, 0, -1):
print(i)
# 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
# Every third number
for i in range(0, 30, 3):
print(i)
# 0, 3, 6, 9, 12, 15, 18, 21, 24, 27
Converting range() to a List
range() is not a list — it is a lazy sequence that generates values on demand. Convert it explicitly when you need a list:
print(range(5)) # range(0, 5) — not a list
print(list(range(5))) # [0, 1, 2, 3, 4]
print(list(range(1, 11))) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Using range() to Loop N Times
The most common use — run something exactly N times:
# Print a separator 30 times
print("-" * 30) # string repetition is faster for this specific case
# But for N arbitrary repetitions:
for _ in range(5):
print("Processing...")
The _ variable name signals that the loop variable is not used — you just need the loop to run N times.
enumerate() — Loops With Index and Value Together
enumerate() wraps any iterable and yields both the index and the value at each step. This eliminates the need for a separate counter variable:
languages = ["Python", "JavaScript", "Java", "Go", "Rust"]
for index, language in enumerate(languages):
print(f"{index}: {language}")
Output:
0: Python
1: JavaScript
2: Java
3: Go
4: Rust
Custom Starting Index
for index, language in enumerate(languages, start=1):
print(f"{index}. {language}")
Output:
1. Python
2. JavaScript
3. Java
4. Go
5. Rust
enumerate() vs manual index counter:
# Without enumerate — verbose and error-prone
i = 0
for language in languages:
print(f"{i}: {language}")
i += 1
# With enumerate — clean and Pythonic
for i, language in enumerate(languages):
print(f"{i}: {language}")
Always use enumerate() when you need both position and value. Creating a manual counter variable is unnecessary in Python.
Real use case — finding positions of matching items:
scores = [88, 92, 71, 95, 68, 88, 91]
target = 88
positions = [i for i, score in enumerate(scores) if score == target]
print(f"Score {target} found at positions: {positions}")
# Score 88 found at positions: [0, 5]
zip() — Iterating Multiple Sequences Together
zip() pairs up items from two or more sequences and yields them together:
names = ["Alice", "Bob", "Carol"]
scores = [88, 92, 79]
grades = ["B", "A", "C"]
for name, score, grade in zip(names, scores, grades):
print(f"{name}: {score} ({grade})")
Output:
Alice: 88 (B)
Bob: 92 (A)
Carol: 79 (C)
zip() stops when the shortest sequence is exhausted. If sequences have different lengths, extra items from longer sequences are ignored:
a = [1, 2, 3, 4, 5]
b = ["a", "b", "c"]
for x, y in zip(a, b):
print(x, y)
# 1 a
# 2 b
# 3 c
# 4 and 5 from a are ignored
Building Dictionaries With zip()
keys = ["name", "age", "city"]
values = ["Priya", 25, "Delhi"]
profile = dict(zip(keys, values))
print(profile) # {'name': 'Priya', 'age': 25, 'city': 'Delhi'}
Iterating Over Dictionaries
Three patterns for looping over dictionaries — all covered in Day 7, reinforced here with loop context:
student = {"name": "Arjun", "age": 21, "gpa": 3.8, "city": "Mumbai"}
# Keys only (default)
for key in student:
print(key)
# Values only
for value in student.values():
print(value)
# Keys and values together — most common
for key, value in student.items():
print(f"{key}: {value}")
Real use case — processing a list of dictionaries:
students = [
{"name": "Alice", "score": 88},
{"name": "Bob", "score": 72},
{"name": "Carol", "score": 95},
]
for student in students:
grade = "A" if student["score"] >= 90 else "B" if student["score"] >= 80 else "C"
print(f"{student['name']}: {student['score']} → {grade}")
Output:
Alice: 88 → B
Bob: 72 → C
Carol: 95 → A
The while Loop — Repeat Until a Condition Fails
A while loop runs its body repeatedly as long as its condition evaluates to True. When the condition becomes False, the loop ends:
count = 1
while count <= 5:
print(f"Count: {count}")
count += 1
print("Loop finished")
Output:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Loop finished
The condition is checked before each iteration. When count reaches 6, count <= 5 is False and the loop stops.
while vs for — When to Use Each
Use for when you know what you are iterating over — a list, a range, a sequence. Use while when you are repeating until something changes and you do not know in advance how many iterations that will take:
# for loop — iterating a known sequence
for item in shopping_cart:
process(item)
# while loop — repeating until a condition changes
attempts = 0
while not connection_established and attempts < 3:
try_connect()
attempts += 1
Input Validation With while
One of the most common real-world uses of while loops:
while True:
age_input = input("Enter your age: ").strip()
if age_input.isdigit():
age = int(age_input)
if 0 < age < 150:
break # valid input — exit the loop
else:
print("Age must be between 1 and 149")
else:
print("Please enter a number")
print(f"Your age is {age}")
The while True combined with break on valid input is the standard Python pattern for "keep asking until the user gives a valid answer."
Countdown and Accumulation Patterns
# Countdown
countdown = 10
while countdown > 0:
print(countdown)
countdown -= 1
print("Launch!")
# Accumulate until threshold
total = 0
transactions = [120, 450, 80, 310, 95, 500, 45]
i = 0
while total < 1000 and i < len(transactions):
total += transactions[i]
print(f"Added {transactions[i]}, running total: {total}")
i += 1
print(f"Stopped at total: {total}")
break — Exit the Loop Immediately
break terminates the loop entirely when executed — Python jumps to the first line after the loop:
numbers = [4, 7, 2, 9, 1, 5, 8, 3]
for number in numbers:
if number == 9:
print(f"Found 9 at index {numbers.index(9)}")
break # stop searching, we found it
print(f"Checked {number}")
Output:
Checked 4
Checked 7
Checked 2
Found 9 at index 3
Search Pattern With break
def find_first_negative(numbers):
for i, num in enumerate(numbers):
if num < 0:
return i, num # return exits function, not just loop
return None, None # not found
data = [5, 12, 8, -3, 7, -1, 4]
index, value = find_first_negative(data)
if index is not None:
print(f"First negative: {value} at index {index}")
# First negative: -3 at index 3
break in while Loops
secret = "python123"
attempts = 0
max_attempts = 3
while attempts < max_attempts:
password = input("Enter password: ")
attempts += 1
if password == secret:
print("Access granted")
break
else:
remaining = max_attempts - attempts
if remaining > 0:
print(f"Wrong password. {remaining} attempts remaining")
else:
print("Account locked — too many failed attempts")
continue — Skip to the Next Iteration
continue skips the rest of the current iteration and moves immediately to the next one. The loop does not end — it just skips this particular pass:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for number in numbers:
if number % 2 == 0:
continue # skip even numbers
print(number)
Output:
1
3
5
7
9
Filtering Invalid Data With continue
raw_data = ["alice", "", "bob", " ", "carol", None, "david"]
valid_names = []
for item in raw_data:
if not item or not item.strip():
continue # skip empty and whitespace-only entries
valid_names.append(item.strip().title())
print(valid_names) # ['Alice', 'Bob', 'Carol', 'David']
continue vs Nested if
Both approaches below produce identical results. continue often produces flatter, more readable code:
# Using nested if
for score in scores:
if score >= 60:
print(f"Pass: {score}")
# Using continue — equivalent, slightly flatter
for score in scores:
if score < 60:
continue
print(f"Pass: {score}")
The continue style is particularly valuable when you have multiple validation checks before the main logic — it keeps the happy path at the left edge rather than deeply indented.
pass — The Placeholder
pass does nothing. It is a syntactic placeholder for a block that Python requires but you have not written yet:
for item in items:
pass # TODO: implement processing later
while condition:
pass # TODO: fill in later
if error:
pass # deliberately ignoring this case for now
pass is most useful during development when you are sketching out structure before filling in logic. In production code, a pass with a comment is usually either a genuine intentional no-op or a reminder to finish something.
else on Loops — The Underused Feature
Python loops have an optional else clause that runs when the loop completes normally — meaning it was not terminated by a break. This is unusual syntax that Python has but most other languages do not:
numbers = [4, 7, 2, 8, 1, 5]
target = 9
for number in numbers:
if number == target:
print(f"Found {target}")
break
else:
print(f"{target} was not found in the list")
# Output: 9 was not found in the list
The else only runs if break was never triggered. If break fires, else is skipped:
numbers = [4, 7, 2, 9, 1, 5]
target = 9
for number in numbers:
if number == target:
print(f"Found {target}")
break
else:
print(f"{target} was not found")
# Output: Found 9 — else does NOT run
This pattern is cleaner than using a boolean flag variable to track whether something was found:
# Without else — requires a flag variable
found = False
for number in numbers:
if number == target:
found = True
break
if not found:
print("Not found")
# With else — no flag needed, intent is clear
for number in numbers:
if number == target:
break
else:
print("Not found")
Nested Loops — Loops Inside Loops
A nested loop has an inner loop that runs completely for each iteration of the outer loop:
for i in range(1, 4):
for j in range(1, 4):
print(f"{i} x {j} = {i * j}")
print() # blank line after each row
Output:
1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
For a 3x3 grid: outer loop runs 3 times, inner loop runs 3 times per outer iteration — 9 total iterations.
Iterating 2D Data
classroom = [
["Alice", 88, 92, 79],
["Bob", 72, 68, 81],
["Carol", 95, 98, 92],
]
for row in classroom:
name = row[0]
scores = row[1:]
average = sum(scores) / len(scores)
print(f"{name}: {scores} → avg {average:.1f}")
Output:
Alice: [88, 92, 79] → avg 86.3
Bob: [72, 68, 81] → avg 73.7
Carol: [95, 98, 92] → avg 95.0
break in Nested Loops
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
target = 5
found = False
for row_index, row in enumerate(matrix):
for col_index, value in enumerate(row):
if value == target:
print(f"Found {target} at row {row_index}, col {col_index}")
found = True
break # exits inner loop only
if found:
break # exits outer loop
Output:
Found 5 at row 1, col 1
Common Loop Patterns
These patterns appear constantly in real Python code.
Accumulation — Building a Total
prices = [29.99, 14.50, 89.99, 5.75, 44.00]
total = 0
for price in prices:
total += price
print(f"Total: £{total:.2f}") # Total: £184.23
Transformation — Building a New List
names = ["alice smith", "bob jones", "carol white"]
formatted = []
for name in names:
formatted.append(name.title())
# More Pythonic with list comprehension
formatted = [name.title() for name in names]
print(formatted) # ['Alice Smith', 'Bob Jones', 'Carol White']
Filtering — Keeping Only Matching Items
scores = [88, 55, 92, 61, 79, 45, 96, 70]
passing = [score for score in scores if score >= 60]
failing = [score for score in scores if score < 60]
print(f"Passing: {passing}")
print(f"Failing: {failing}")
Searching — Finding the First Match
def find_first_above(values, threshold):
for i, value in enumerate(values):
if value > threshold:
return i, value
return None, None
readings = [12, 18, 24, 31, 19, 28, 35]
index, value = find_first_above(readings, 30)
print(f"First reading above 30: {value} at index {index}")
# First reading above 30: 31 at index 3
Flattening — Combining Nested Lists
nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
flat = []
for sublist in nested:
for item in sublist:
flat.append(item)
# With list comprehension
flat = [item for sublist in nested for item in sublist]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Frequency Counting
words = ["python", "java", "python", "go", "java", "python", "rust"]
frequency = {}
for word in words:
frequency[word] = frequency.get(word, 0) + 1
for language, count in sorted(frequency.items(), key=lambda x: x[1], reverse=True):
print(f"{language}: {count}")
Output:
python: 3
java: 2
go: 1
rust: 1
A Complete Working Program
Here is a student exam analyser that uses every loop concept from this chapter:
# Student exam analyser
exam_data = [
{"name": "Priya Sharma", "scores": [88, 92, 79, 95, 84]},
{"name": "Arjun Mehta", "scores": [72, 65, 81, 70, 68]},
{"name": "Cleo Park", "scores": [95, 98, 92, 97, 99]},
{"name": "Ravi Kumar", "scores": [55, 60, 48, 62, 58]},
{"name": "Maya Torres", "scores": [83, 87, 90, 85, 88]},
]
def analyse_student(student):
scores = student["scores"]
average = sum(scores) / len(scores)
highest = max(scores)
lowest = min(scores)
if average >= 90: grade = "A"
elif average >= 80: grade = "B"
elif average >= 70: grade = "C"
elif average >= 60: grade = "D"
else: grade = "F"
return average, highest, lowest, grade
# Process all students
results = []
for student in exam_data:
avg, high, low, grade = analyse_student(student)
results.append({
"name": student["name"],
"average": avg,
"highest": high,
"lowest": low,
"grade": grade
})
# Print results table
print(f"{'Name':<16} {'Avg':>6} {'High':>6} {'Low':>5} {'Grade':>6}")
print("-" * 45)
for r in results:
print(f"{r['name']:<16} {r['average']:>6.1f} {r['highest']:>6} "
f"{r['lowest']:>5} {r['grade']:>6}")
# Class statistics using loops
all_averages = [r["average"] for r in results]
class_avg = sum(all_averages) / len(all_averages)
top_student = max(results, key=lambda x: x["average"])
failing = [r for r in results if r["grade"] == "F"]
# Find students who improved (last score higher than first)
print(f"\n=== Class Summary ===")
print(f"Class average: {class_avg:.1f}")
print(f"Top performer: {top_student['name']} ({top_student['average']:.1f})")
print(f"Failing students: {len(failing)}")
# Check if any student is failing — loop with else
print(f"\nFailing student check:")
for student in exam_data:
avg = sum(student["scores"]) / len(student["scores"])
if avg < 60:
print(f" {student['name']} needs support (avg: {avg:.1f})")
break
else:
print(" No students are currently failing")
Output:
Name Avg High Low Grade
---------------------------------------------
Priya Sharma 87.6 95 79 B
Arjun Mehta 71.2 81 65 C
Cleo Park 96.2 99 92 A
Ravi Kumar 56.6 62 48 F
Maya Torres 86.6 90 83 B
=== Class Summary ===
Class average: 79.6
Top performer: Cleo Park (96.2)
Failing students: 1
Failing student check:
Ravi Kumar needs support (avg: 56.6)
5 Loop Mistakes Every Beginner Makes
Mistake 1: Infinite while loop — forgetting to update the condition
# Wrong — count never changes, loops forever
count = 1
while count <= 5:
print(count)
# forgot count += 1
# Correct
count = 1
while count <= 5:
print(count)
count += 1 # must update or condition never becomes False
If your program hangs without output, an infinite loop is the first thing to check. Press Ctrl+C to interrupt it.
Mistake 2: Modifying a list while iterating over it
numbers = [1, 2, 3, 4, 5, 6]
# Wrong — skips items unpredictably
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers) # [1, 3, 5] — looks right but only by coincidence
# Safe — iterate over a copy
for n in numbers[:]:
if n % 2 == 0:
numbers.remove(n)
# Cleanest — use a comprehension
numbers = [n for n in numbers if n % 2 != 0]
Mistake 3: Using range(len(list)) when enumerate() is cleaner
items = ["apple", "banana", "cherry"]
# Verbose — old style
for i in range(len(items)):
print(f"{i}: {items[i]}")
# Pythonic — use enumerate
for i, item in enumerate(items):
print(f"{i}: {item}")
range(len(list)) is not wrong — but enumerate() is the Python way. Interviewers and code reviewers notice the difference.
Mistake 4: Off-by-one errors with range()
# Print numbers 1 to 10
for i in range(10): # wrong — prints 0 to 9
print(i)
for i in range(1, 10): # wrong — prints 1 to 9, misses 10
print(i)
for i in range(1, 11): # correct — prints 1 to 10
print(i)
Remember: range(start, stop) includes start but excludes stop. To include 10, stop must be 11.
Mistake 5: Expecting break to exit all nested loops
# break only exits the innermost loop
for i in range(3):
for j in range(3):
if j == 1:
break # exits inner loop only
print(f"i={i} still runs") # outer loop continues
# To exit both loops — use a flag or restructure into a function
def find_in_matrix(matrix, target):
for i, row in enumerate(matrix):
for j, val in enumerate(row):
if val == target:
return i, j # return exits the function entirely
return None, None
Practice: Loop Exercises
Exercise 1: for loop fundamentals
Write a program using a for loop and range() that prints the multiplication table for any number between 1 and 10. Format it cleanly: 7 x 1 = 7, 7 x 2 = 14, etc.
Exercise 2: while loop with validation
Write a number guessing game. Generate a secret number between 1 and 100. Use a while loop to keep asking the user to guess. Print "Too high", "Too low", or "Correct!" on each attempt. Count and display the number of attempts at the end.
Exercise 3: enumerate() and zip()
Given two lists — student names and their scores — use zip() to pair them, then enumerate() to add a ranking number. Print a ranked leaderboard sorted by score descending.
Exercise 4: Loop patterns
Given transactions = [250, -80, 500, -120, 300, -50, 1000, -200] (positive = deposit, negative = withdrawal), use a loop to calculate the final balance, total deposited, total withdrawn, and the number of each transaction type.
Exercise 5: Nested loops — pattern printing
Using nested loops, print the following pattern for n=5:
*
* *
* * *
* * * *
* * * * *
Exercise 6: Word frequency
Given a paragraph of text, write a program that counts how many times each word appears (case-insensitive, ignoring punctuation). Print the top 5 most frequent words.
→ See all Python Loop exercises with solutions
For all topics, see Python exercises.
What Comes Next — Day 10: Functions
You can now write Python programs that store data, make decisions, and repeat operations automatically. The next step is organisation. Functions let you give a name to a block of code and call it whenever you need it — once defined, a function runs anywhere you call it, with any input you give it.
Day 10 covers:
- Defining functions with
def - Parameters and return values
- Default arguments and keyword arguments
*argsand**kwargsfor flexible functions- Variable scope — local vs global
- Why well-designed functions are the foundation of maintainable code
→ Continue to Day 10: Functions
Chapter navigation
- Previous: Day 8: Conditional Statements
- Next: Day 10: Functions
Frequently asked questions: Loops and iteration
What is the difference between a for loop and a while loop in Python?
A for loop iterates over a sequence — a list, string, range, or any iterable — and runs once for each item. Use it when you know what you are iterating over. A while loop repeats as long as a condition is True — use it when you are repeating until something changes and you do not know in advance how many iterations that requires.
What does range() do in Python?
range() generates a sequence of integers lazily — without creating them all in memory. range(n) generates 0 to n-1. range(start, stop) generates from start to stop-1. range(start, stop, step) generates with a custom step size, including negative steps for counting backwards. Convert to a list with list(range(...)) when you need a list.
What is enumerate() and why should I use it?
enumerate() wraps any iterable and yields both the index and the value at each step: for i, item in enumerate(items). Use it whenever you need both the position and the value in a loop. It replaces the verbose pattern of creating a counter variable manually and incrementing it each iteration.
What is the difference between break and continue in Python loops?
break exits the loop entirely — execution jumps to the first line after the loop. continue skips only the current iteration — execution jumps to the top of the loop for the next iteration. break is used to stop searching once a match is found. continue is used to skip items that do not meet a condition while processing the rest.
How do I avoid infinite loops in Python?
An infinite loop occurs when a while loop condition never becomes False. Always ensure the loop body contains code that eventually makes the condition false — typically by updating a counter (count += 1), modifying the variable being tested, or using break when a termination condition is met. If a program hangs, press Ctrl+C to interrupt it.
What does the else clause on a Python loop do?
A loop else clause runs when the loop completes normally — meaning it was not terminated by break. It does not run if break fired. This is most useful in search patterns: put the not-found logic in the else block and it only runs if the entire sequence was searched without finding a match.
What is zip() in Python and when should I use it?
zip() pairs up items from two or more sequences and yields them together as tuples. Use it when you have related data in separate lists and need to process them together. zip() stops at the shortest sequence. The dict(zip(keys, values)) pattern is a clean way to build dictionaries from two parallel lists.
How do I exit all levels of nested loops in Python?
break only exits the innermost loop. To exit all levels, the cleanest approach is to move the nested loops into a function and use return — which exits the function entirely. Alternatively, use a boolean flag variable that the outer loop checks.
Related Page
Day 8: Conditional Statements
Learn about day 8: conditional statements
Learn MoreDay 10: Functions
Continue with day 10: functions
Learn MorePython exercises hub
165+ programs with solutions
Learn MorePython quiz
Multiple-choice checks with explanations
Learn MorePython loop programs
for/while, patterns, and iteration practice
Learn More