Table of Contents
ToggleA for loop is Python’s clean, readable way to repeat work over a series of items. In this section, we’ll build your intuition for what a for loop really does, how Python decides “what comes next,” and why it’s different from a classic counter loop. By the end, you’ll be able to explain for loops in your own words and spot better loop designs.
for loop (in plain English)?
A for loop says: “Give me items from this container one by one; I’ll run my block for each item.”
That “container” could be a list, a string, a tuple, a set, a dictionary, a file, a
range of numbers, or even a generator that produces values on the fly.
Unlike some languages that require manual indexing, Python’s for focuses on the items themselves —
the loop gives you each item directly.
fruits = ["apple", "banana", "mango"]
for fruit in fruits:
print(fruit)
# prints:
# apple
# banana
# mango
Here, fruits is the container (technically, an iterable),
and fruit is the loop variable that takes on each value in turn.
An iterable is any object you can loop over. Behind the scenes, Python uses a simple handshake:
iter(iterable) to get an iterator object.next(iterator) to fetch the next item.StopIteration, and the loop ends.
data = [10, 20, 30]
it = iter(data) # step 1: get iterator
print(next(it)) # step 2: get 10
print(next(it)) # step 2: get 20
print(next(it)) # step 2: get 30
# next(it) now would raise StopIteration
for vs while: when to choose which?
Use for when you have a collection of items to process or a known sequence of values
(e.g., numbers from 1 to 100). Use while when the loop depends on a condition that changes over time
and may not be tied to a simple sequence (e.g., wait for user input until they type “quit”).
for when…range().while when…
# for loop: “do this for each item”
for n in [2, 4, 6]:
print(n)
# while loop: “repeat while condition is true”
count = 0
while count < 3:
print(count)
count += 1
for loop (step-by-step)range(5), open("file.txt"), a list, etc.).iter(iterable) to get an iterator.next(iterator).StopIteration occurs.else clause runs if no break happened (we’ll deep-dive later).
names = ["Asha", "Biren", "Charu"]
for name in names: # Step 1–2: iterator created internally
print(name) # Step 3–5: each item printed
# Step 6–7: loop exits when items end
The name after for (e.g., item, name) is the loop variable.
Each iteration assigns a new value to this variable. In Python, the loop variable still exists after the loop finishes —
it holds the last value from the iteration. Keep that in mind if you reuse the same name later.
for letter in "abc":
pass
print(letter) # 'c' — last value seen in the loop
_ if you don’t care about the loop variable (e.g., for _ in range(3):),
or pick descriptive names like user, row, product.
Problem: Count how many numbers in a list are even. Approach: Loop through the items, and increment a counter when the condition is true.
nums = [1, 2, 3, 4, 5, 6]
count_even = 0
for n in nums:
if n % 2 == 0:
count_even += 1
print("even:", count_even) # even: 3
Problem: Create a list of squares from a list of numbers. Approach: Start with an empty list and append results each iteration.
nums = [2, 3, 5]
squares = []
for n in nums:
squares.append(n * n)
print(squares) # [4, 9, 25]
Problem: Count vowels in a word (case-insensitive). Approach: Loop each char, normalize case, test membership.
word = "Education"
vowels = 0
for ch in word.lower():
if ch in "aeiou":
vowels += 1
print("vowels:", vowels) # 5
Clean loops are about clear intent. Prefer expressive names, keep the body focused, and pull complex logic into helper functions. Add short comments for non-obvious steps.
# Good: intent is obvious
for student in classroom:
if student.attendance >= 0.75:
award_points(student)
# Less clear: what is x? what is y?
for x in arr:
if y(x) >= z:
f(x)
enumerate() only when you actually need the index.range() excludes the stop value: range(1, 5) yields 1,2,3,4 (not 5). We’ll master range() in Section 3.iter() return? What does next() do?while loop instead of a for loop?vals = [-3, 0, 4, 7, -1, 9], count how many are > 0.text = "AbcDefGhI", build a string containing only lowercase letters (keep order).for x in [1,2,3]: pass then print x. What do you see? Why?iter() and next() on a short list and a short string.End of Section 1. In Section 2, we’ll write the formal syntax, explore execution nuances, and compare patterns with richer examples.
In this section, we’ll go beyond the intuition you built in Section 1 and master the exact syntax of a Python
for loop. You’ll learn how Python steps through an iterable, how indentation controls the loop’s “body”,
how to use multiple assignment (destructuring) in the loop variable, and how to dry-run a loop like a pro.
for <target> in <iterable>:
<suite>
x or a, b).: ends the header line. The suite must be indented consistently (spaces preferred).
Everything indented under the for line is part of the loop. When indentation ends, the loop body ends.
Most teams use 4 spaces per indent. Mixing tabs and spaces can cause errors—stick to spaces.
cities = ["Delhi", "Mumbai", "Pune"]
for city in cities:
# ↓ everything here runs once per city
print(city.upper())
print(len(city))
print("Done") # ← not indented: runs once after loop ends
iter(<iterable>) to obtain an iterator.next(iterator). If an item is returned, assign it to <target>.next() raises StopIteration.for ... else clause (covered later) runs if no break occurred.
nums = [2, 4, 6]
# Python does this internally:
it = iter(nums) # create iterator
x = next(it) # x = 2
print(x) # prints 2
x = next(it) # x = 4
print(x) # prints 4
x = next(it) # x = 6
print(x) # prints 6
# next(it) now raises StopIteration → loop ends
The loop target can be a single name (x) or a pattern that unpacks each item
into multiple names (called destructuring or tuple unpacking). This makes loops concise and expressive.
pairs = [("Asha", 91), ("Biren", 88), ("Charu", 95)]
for name, score in pairs:
print(f"{name} scored {score}")
# Asha scored 91
# Biren scored 88
# Charu scored 95
records = [("p1", "Laptop", 58000), ("p2", "Mouse", 600)]
for _id, title, price in records: # use _ when you don't need a name later
print(title, "→", price)
enumerate() (index + value)
cities = ["Agra", "Delhi", "Panaji"]
for idx, city in enumerate(cities, start=1):
print(idx, city)
# 1 Agra
# 2 Delhi
# 3 Panaji
zip()
subjects = ["Math", "Science", "English"]
marks = [90, 85, 92]
for sub, m in zip(subjects, marks):
print(sub, m)
# Math 90
# Science 85
# English 92
for a, b in [1, 2, 3]),
you’ll get a ValueError: not enough values to unpack. Always confirm the structure.
list, tuple, range, str (string)set, dict (dict iterates keys by default)
# Strings: character by character
for ch in "hello":
print(ch)
# Dicts: keys by default (use .values() / .items() if needed)
info = {"name": "Asha", "marks": 91}
for key in info:
print("key:", key)
for val in info.values():
print("val:", val)
for key, val in info.items():
print(key, "→", val)
# Files: each iteration gives you one line (with trailing newline)
# with open("data.txt") as f:
# for line in f:
# process(line)
Tracing helps you predict outputs and catch logic errors early. Use these steps:
nums = [1, 2, 3, 4, 5]
even_squares = []
for n in nums: # n: 1 → 2 → 3 → 4 → 5
sq = n * n # sq: 1 → 4 → 9 → 16 → 25
if sq % 2 == 0:
even_squares.append(sq)
print(even_squares) # [4, 16]
print() statements or use a debugger breakpoint inside the loop to inspect
values live. Remove prints once the logic is solid.
for x in xs: not for x in xs.IndentationError..values() or .items() explicitly.pairs = [(\"red\", 1), (\"blue\", 2), (\"green\", 3)], print “color=value” for each using for color, value in pairs.names and marks lists, print Aman: 85 style lines using zip().iter()/next() as shown above and observe StopIteration..items(). Then repeat using keys and a lookup.
End of Section 2. In Section 3, we’ll master range(): start/stop/step, negative steps, off-by-one traps,
and practical counted loop patterns.
range() Function (Start, Stop, Step)
range() is the backbone of counted loops in Python. It creates a sequence of integers lazily (memory-efficient) and
gives you precise control over how many times the loop runs and what values you iterate.
range() produces (and why it matters)len(), indexing, and slicing.
# Visualise the values by converting to a list (only for small sizes)
print(list(range(5))) # [0, 1, 2, 3, 4]
print(list(range(2, 7))) # [2, 3, 4, 5, 6]
print(list(range(10, 0, -3))) # [10, 7, 4, 1]
range(stop) # 0, 1, 2, ..., stop-1
range(start, stop) # start, ..., stop-1
range(start, stop, step) # step can be positive or negative (but not 0)
0.0 (raises ValueError).Classic “repeat N times” pattern—no index math needed.
N = 5
for _ in range(N):
print("Hello")
# prints Hello 5 times
Need 2..6? Use range(2, 7)—remember: stop is exclusive.
for i in range(2, 7):
print(i, end=" ")
# 2 3 4 5 6
Every 2nd number, or every 3rd—use the third argument.
for i in range(0, 10, 2): # even numbers under 10
print(i, end=" ")
# 0 2 4 6 8
To go backwards, make step negative and make sure start > stop.
for i in range(5, 0, -1):
print(i, end=" ")
# 5 4 3 2 1
For understanding: convert a small range to a list and print it. For large ranges, don’t convert—iterate directly.
r1 = range(10) # 0..9
print(len(r1)) # 10
print(r1[0], r1[-1]) # 0, 9
print(list(r1[:4])) # [0, 1, 2, 3] (slicing works)
range(1_000_000) is cheap; list(range(1_000_000)) is not. Convert only for display or when you truly need a materialized list.
When you actually need positions (e.g., write back to the same list), iterate indices:
nums = [10, 20, 30, 40]
for i in range(len(nums)):
nums[i] += 1
print(nums) # [11, 21, 31, 41]
for x in nums) unless you need indices for mutation or cross-referencing.
for _ in range(3):
do_something()
evens = [i for i in range(0, 21, 2)]
odds = [i for i in range(1, 21, 2)]
print(evens) # [0, 2, 4, ..., 20]
print(odds) # [1, 3, 5, ..., 19]
Useful for matrices, pixel grids, timetable slots:
rows, cols = 3, 4
for r in range(rows):
for c in range(cols):
print(f"cell({r},{c})", end=" ")
print()
# cell(0,0) cell(0,1) cell(0,2) cell(0,3)
# cell(1,0) cell(1,1) cell(1,2) cell(1,3)
# cell(2,0) cell(2,1) cell(2,2) cell(2,3)
Jump by chunk size to process in fixed batches:
data = list(range(1, 21)) # 1..20
chunk = 5
for start in range(0, len(data), chunk):
batch = data[start:start+chunk]
print(batch)
# [1,2,3,4,5]
# [6,7,8,9,10]
# [11,12,13,14,15]
# [16,17,18,19,20]
Two good ways to go backwards:
range(high, low, -1)reversed(): reverse an existing sequence or range
for i in range(10, 0, -2):
print(i, end=" ")
# 10 8 6 4 2
for i in reversed(range(1, 6)):
print(i, end=" ")
# 5 4 3 2 1
range(0, 10, -1) yields nothing because start < stop with a negative step. For negative steps,
make sure start > stop.
start is already beyond stop in the direction of step, you get zero iterations.range(1, 5, 0) raises ValueError. Step must be non-zero.U, use range(L, U+1) for positive steps.
for i in range(5, 5): # empty
print("won't run")
# include 100 in the loop:
for i in range(1, 101): # 1..100 inclusive
pass
range() with enumerate() and zip()enumerate: add a human-friendly counter to any iterable (start at 1 if you like).
items = ["alpha", "beta", "gamma"]
for idx, item in enumerate(items, start=1):
print(idx, item)
# 1 alpha
# 2 beta
# 3 gamma
zip: iterate multiple ranges together (e.g., coordinates, paired counters):
for x, y in zip(range(3), range(100, 103)):
print(x, y)
# 0 100
# 1 101
# 2 102
range() to compute the sum (hint: initialize total=0 then loop).n=7 using for i in range(1, 11).(r,c) pairs with nested range loops.reversed(range(1, 6)) and compare to a negative step loop.list(range(...)) on a tiny example to verify the sequence,
then switch back to plain range() for the real loop.
End of Section 3. Next up, Section 4 will cover loop control: break, continue, pass, and the powerful for…else pattern with practical search/validation tasks.
break, continue, pass & for…else
Mastering loop control lets you write loops that are shorter, faster, and easier to reason about. Learn how to stop early, skip work, leave placeholders, and run a block only if the loop didn’t break.
break — stop the loop early
break immediately exits the innermost loop. Code after the loop continues normally.
Use it when you’ve found what you were looking for or further work is pointless.
nums = [3, 9, 12, 7, 18, 42, 5]
target = 42
for n in nums:
if n == target:
print("Found:", n)
break
print("Checked:", n)
print("Search finished")
for r in range(3):
for c in range(5):
if (r, c) == (1, 2):
print("Hit at", r, c, "— breaking inner loop")
break # exits the inner 'for c' loop only
print("Row", r, "done")
continue — skip the rest of this iteration
continue jumps straight to the next iteration of the loop. Anything below it in the current iteration is skipped.
Use it to avoid deep nesting and make “happy path” logic clearer.
rows = [
{"name": "Asha", "marks": 91},
{"name": "Biren"}, # missing marks
{"name": "Charu", "marks": 88}
]
for row in rows:
if "marks" not in row:
# no marks → skip this row
continue
print(row["name"], row["marks"])
for i in range(10):
if i % 2 == 0:
continue
print(i) # 1 3 5 7 9
continue often read better than nesting every step inside an if.
pass — a do-nothing placeholder
pass is a statement that does nothing. It’s useful when you need a syntactically valid block but
you haven’t implemented it yet (or you want to ignore a case temporarily).
for _ in range(3):
pass # TODO: add logic later
print("Loop ran 3 times")
for ch in "abc_def":
if ch == "_":
pass # intentionally ignore underscore
else:
print(ch, end="")
# prints: abcdef
for…else — run a block only if the loop didn’t break
The else of a for runs after the loop finishes normally (i.e., without hitting break).
If you used break, the else block is skipped.
names = ["Aman", "Beena", "Charu"]
target = "Dinesh"
for name in names:
if name == target:
print("Found:", name)
break
else:
print("Not found") # runs only if loop didn't break
emails = ["a@x.com", "ok@y.org", "bad at gmail", "z@z.in"]
for e in emails:
if "@" not in e:
print("Invalid:", e)
break
else:
print("All emails valid!") # only if no invalid found
else pairs with the for, not the if inside it.
It means “no break happened,” not “condition was false.”
break when you can stop as soon as a condition is met (search/find/terminate early).continue to skip “bad” or unneeded cases and keep the loop body clean.pass to leave a block empty while keeping the code valid.for…else for “no-hits” logic: do X if nothing triggered a break.for…else: It’s “no break,” not “if condition false.”return to exit both loops cleanly.continue: Too many guard clauses can fragment logic—balance readability.pass explaining why it’s empty (or log once).break).continue).break. If the loop finishes, print “all strong” (for…else).pass and a comment.return the coordinates.break, continue, or for…else to match the intent and simplify the code.
End of Section 4. In Section 5, you’ll iterate over real data types (strings, lists, tuples, sets, dicts) the smart way:
with indexing via enumerate(), key/value pairs, and safe mutation patterns.
You now know how for loops work and how to count with range(). In this section we’ll loop through the
most common Python data structures, learn when to use enumerate(), how to unpack tuple-like items, and how to mutate
data safely while iterating.
Strings are sequences of Unicode characters. Iterating a string yields one character at a time. This is ideal for counting, filtering, and building transformed strings.
text = "Artificial Intelligence"
vowels = 0
for ch in text.lower():
if ch in "aeiou":
vowels += 1
print("vowels:", vowels) # 11
s = "Hello, world! How's it going?"
clean = []
for ch in s:
if ch.isalnum() or ch.isspace():
clean.append(ch)
result = "".join(clean)
print(result) # Hello world Hows it going
''.join()).
Lists are ordered and mutable. Prefer iterating items directly; switch to indices when you need in-place updates.
nums = [3, 6, 9]
total = 0
for n in nums:
total += n
print(total) # 18
prices = [100, 200, 300]
for i in range(len(prices)):
prices[i] *= 1.18 # add 18% tax
print(prices) # [118.0, 236.0, 354.0]
data = [10, -2, 0, 15, -5]
positives = []
for x in data:
if x > 0:
positives.append(x)
print(positives) # [10, 15]
for x in list(items): ...
Many datasets are lists of tuples (rows). Unpack fields directly in the loop target to keep code readable.
items = [("pen", 20), ("pencil", 10), ("folder", 50)]
for name, price in items:
print(f"{name} → {price}")
_)
rows = [("P001", "Notebook", 99), ("P002", "Bag", 799)]
for _id, title, price in rows:
print(title, "—", price)
orders = [("INV1", ("Asha", "IN")), ("INV2", ("Biren", "US"))]
for invoice, (customer, country) in orders:
print(invoice, customer, country)
ValueError. Confirm the structure first.
Sets store unique elements and iterate in an arbitrary order (insertion order is not a guarantee you should rely on for logic). Use sets for membership tests and de-duplication.
sentence = "to be or not to be"
unique = set()
for w in sentence.split():
unique.add(w)
print(unique) # {'to', 'be', 'or', 'not'}
allowed = {"jpg", "png", "gif"}
files = ["a.jpg", "b.txt", "c.png"]
for f in files:
ext = f.split(".")[-1]
if ext in allowed:
print("OK:", f)
x in my_set is usually O(1), much faster than searching a list for large datasets.
Dictionaries map keys to values. By default, iterating a dict gives you the keys. Use .values() or
.items() when you need values or key/value pairs.
student = {"name": "Asha", "marks": 91}
for k in student:
print(k, "→", student[k])
for v in student.values():
print(v)
for key, val in student.items():
print(key, "→", val)
bill = {"burger": 120, "fries": 80, "shake": 90}
total = 0
for item, price in bill.items():
total += price
print("total:", total)
enumerate()
enumerate() gives you (index, value) pairs without manual counters. It’s perfect for
displaying positions, writing back to the same list (with indices), or labeling items.
names = ["Aman", "Beena", "Charu"]
for i, name in enumerate(names, start=1):
print(i, name)
# 1 Aman
# 2 Beena
# 3 Charu
scores = [35, 78, 49, 90]
for i, s in enumerate(scores):
if s < 50:
scores[i] = 50 # minimum passing mark
print(scores) # [50, 78, 50, 90]
filtered = [x for x in data if cond(x)]) or append in a loop.enumerate() to write back safely.for x in list(items): ...) or collect indices to remove later..items(); collect changes and apply after, or iterate a list of keys().
nums = [5, -1, 3, -2, 9]
# Good: build new list
clean = [x for x in nums if x >= 0]
print(clean) # [5, 3, 9]
# If you must mutate the same list:
for i in range(len(nums)-1, -1, -1): # iterate backwards
if nums[i] < 0:
del nums[i]
print(nums) # [5, 3, 9]
[("Jan", 12000), ("Feb", 8000), ...], print “Month → amount” using tuple unpacking.names and marks lists, create a dict using zip(), then iterate .items() to print results.None by iterating a list of keys and deleting safely.enumerate(..., start=1).enumerate() for index+value; use tuple unpacking for structured rows; use dict.items() for key/value pairs.End of Section 5. In Section 6, we’ll tackle nested loops, grid patterns, and performance tips to keep nested logic clean and fast.
