Python Loops

Master Python iteration: for and while loops, range(), enumerate(), zip(), break, continue, and the loop else clause.

Beginner 11 min read 10 examples

for Loops

Python's for loop iterates directly over any iterable - no index management needed.

Python
# Iterate over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)
# apple
# banana
# cherry

# Iterate over a string (character by character)
for char in "Python":
    print(char, end=" ")    # P y t h o n

# Iterate over a dict (keys by default)
person = {"name": "Alice", "age": 30}
for key in person:
    print(key, person[key])

# Iterate over dict key-value pairs
for key, value in person.items():
    print(f"{key}: {value}")

# Iterate over a tuple
for x, y in [(1, 2), (3, 4), (5, 6)]:
    print(f"({x}, {y})")

# Iterate over a set (unordered)
for item in {3, 1, 4, 1, 5}:
    print(item)     # arbitrary order

# Loop over multiple items with unpacking
records = [("Alice", 95), ("Bob", 87), ("Charlie", 92)]
for name, score in records:
    print(f"{name}: {score}")

# Use _ for ignored loop variable
for _ in range(3):
    print("hello")      # prints 3 times, don't need the index

range()

range() generates a sequence of numbers lazily - it does not store them all in memory.

Python
# range(stop) - 0 to stop-1
for i in range(5):
    print(i, end=" ")   # 0 1 2 3 4

# range(start, stop) - start to stop-1
for i in range(2, 8):
    print(i, end=" ")   # 2 3 4 5 6 7

# range(start, stop, step)
for i in range(0, 20, 5):
    print(i, end=" ")   # 0 5 10 15

# Countdown with negative step
for i in range(10, 0, -1):
    print(i, end=" ")   # 10 9 8 7 6 5 4 3 2 1

# range() is NOT a list - it's a range object
r = range(5)
print(type(r))          # 
print(r[2])             # 2  (supports indexing)
print(3 in r)           # True
print(list(r))          # [0, 1, 2, 3, 4]  (convert if needed)

# Loop with index over a list - use enumerate() instead of range(len())
fruits = ["apple", "banana", "cherry"]

# WRONG (un-Pythonic):
for i in range(len(fruits)):
    print(i, fruits[i])

# RIGHT (Pythonic):
for i, fruit in enumerate(fruits):
    print(i, fruit)

# Reverse iteration
for i in range(len(fruits) - 1, -1, -1):
    print(fruits[i])

# Better: reversed()
for fruit in reversed(fruits):
    print(fruit)

while Loops

while loops run as long as the condition is truthy. Use them when the number of iterations is not known in advance.

Python
# Basic while loop
count = 0
while count < 5:
    print(count, end=" ")   # 0 1 2 3 4
    count += 1

# Sentinel value pattern
total = 0
while True:
    entry = input("Enter number (0 to stop): ")
    num = int(entry)
    if num == 0:
        break
    total += num
print(f"Total: {total}")

# Walrus operator in while (Python 3.8+)
while line := input("Type something (blank to quit): "):
    print(f"You typed: {line}")

# Retry pattern
import random
import time

attempts = 0
max_attempts = 5

while attempts < max_attempts:
    if random.random() > 0.7:   # 30% chance of success
        print("Success!")
        break
    attempts += 1
    print(f"Attempt {attempts} failed, retrying...")
    # time.sleep(1)  # in real code
else:
    print("All attempts exhausted")

# Process a queue
queue = [1, 2, 3, 4, 5]
while queue:
    item = queue.pop(0)     # process first item
    print(f"Processing {item}")
    # queue might be repopulated in real code
Always ensure while loops terminate

An infinite loop with no exit condition will hang your program. Always check that: (1) the loop variable advances toward the exit condition, or (2) a break is reachable. Add a maximum iteration count as a safety net for production retry loops.

enumerate() and zip()

Python
# --- enumerate() ---
# Adds index to any iterable

fruits = ["apple", "banana", "cherry"]

for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry

# Start index at 1
for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry

# --- zip() ---
# Iterates over multiple iterables simultaneously

names  = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
grades = ["A", "B", "A"]

for name, score, grade in zip(names, scores, grades):
    print(f"{name}: {score} ({grade})")
# Alice: 95 (A)
# Bob: 87 (B)
# Charlie: 92 (A)

# zip() stops at the shortest iterable
short = [1, 2]
long  = [10, 20, 30, 40]
print(list(zip(short, long)))   # [(1, 10), (2, 20)]

# zip_longest continues to the end, filling with None
from itertools import zip_longest
print(list(zip_longest(short, long, fillvalue=0)))
# [(1, 10), (2, 20), (0, 30), (0, 40)]

# Unzip - transpose a list of tuples
pairs = [(1, "a"), (2, "b"), (3, "c")]
nums, chars = zip(*pairs)       # *pairs unpacks the list
print(nums)     # (1, 2, 3)
print(chars)    # ('a', 'b', 'c')

# Build a dict from two lists
keys   = ["name", "age", "city"]
values = ["Alice", 30, "London"]
person = dict(zip(keys, values))
print(person)   # {'name': 'Alice', 'age': 30, 'city': 'London'}

# Combine enumerate and zip
for i, (name, score) in enumerate(zip(names, scores), 1):
    print(f"{i}. {name} - {score}")

break, continue, and else

Python
# break - exit the loop immediately
for n in range(10):
    if n == 5:
        break
    print(n, end=" ")   # 0 1 2 3 4

# continue - skip rest of current iteration, go to next
for n in range(10):
    if n % 2 == 0:
        continue    # skip even numbers
    print(n, end=" ")   # 1 3 5 7 9

# else on a for loop - runs if loop completed without break
numbers = [2, 4, 6, 8, 10]

for n in numbers:
    if n % 2 != 0:
        print(f"Found odd: {n}")
        break
else:
    print("All numbers are even")   # runs because no break occurred

# else for "search and not found"
target = 7
haystack = [1, 3, 5, 9, 11]

for item in haystack:
    if item == target:
        print(f"Found {target}")
        break
else:
    print(f"{target} not found in list")   # runs - 7 is not there

# break in while loop with else
attempts = 0
while attempts < 5:
    success = False  # simulate
    if success:
        print("Connected")
        break
    attempts += 1
else:
    print("Could not connect after 5 attempts")

# Nested loops - break only exits innermost loop
for i in range(3):
    for j in range(3):
        if j == 1:
            break           # only breaks inner loop
        print(f"({i},{j})", end=" ")
print()
# (0,0) (1,0) (2,0)

# To break outer loop from inner: use a flag or function
def find_pair(matrix, target):
    for i, row in enumerate(matrix):
        for j, val in enumerate(row):
            if val == target:
                return i, j     # return exits the function
    return None

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(find_pair(matrix, 5))     # (1, 1)

Common Loop Patterns

Python
import itertools

# 1. Accumulate a result
numbers = [1, 2, 3, 4, 5]
total   = sum(numbers)      # prefer built-in
product = 1
for n in numbers:
    product *= n
print(total, product)   # 15 120

# 2. Flatten a nested list
nested = [[1, 2], [3, 4], [5, 6]]
flat   = [item for sublist in nested for item in sublist]
# or: list(itertools.chain.from_iterable(nested))

# 3. Group consecutive items (chunks)
data   = list(range(10))
n      = 3
chunks = [data[i:i+n] for i in range(0, len(data), n)]
print(chunks)   # [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

# 4. Sliding window
window_size = 3
windows = [data[i:i+window_size] for i in range(len(data) - window_size + 1)]
print(windows[:3])  # [[0, 1, 2], [1, 2, 3], [2, 3, 4]]

# 5. Count occurrences without Counter
words  = "the quick brown fox jumps over the lazy dog".split()
counts = {}
for word in words:
    counts[word] = counts.get(word, 0) + 1
print(sorted(counts.items(), key=lambda x: x[1], reverse=True)[:3])
# [('the', 2), ('quick', 1), ...]

# 6. Find first matching item
def first_match(iterable, predicate):
    for item in iterable:
        if predicate(item):
            return item
    return None

result = first_match([1, 3, 5, 8, 11], lambda x: x % 2 == 0)
print(result)   # 8

# Or with next() + generator expression
result2 = next((x for x in [1, 3, 5, 8, 11] if x % 2 == 0), None)
print(result2)  # 8

# 7. Parallel iteration with index
names  = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]

for i, (name, score) in enumerate(zip(names, scores), 1):
    rank = "Gold" if i == 1 else "Silver" if i == 2 else "Bronze"
    print(f"{rank}: {name} ({score})")

Frequently Asked Questions

A loop's else clause runs when the loop finishes without hitting a break. If break is reached, the else is skipped. This is useful for "search and not found" patterns: loop through items, break when found, the else runs only if nothing was found. It is an unusual construct - most developers use a flag variable instead for clarity.

Use enumerate(): for i, item in enumerate(my_list):. You can specify a starting index: enumerate(my_list, start=1). Never use range(len(my_list)) and then index into the list - that is un-Pythonic and error-prone. The Pythonic way is always enumerate().

Use zip(): for a, b in zip(list1, list2):. By default, zip() stops at the shorter sequence. To continue to the end of the longer sequence (padding with None), use itertools.zip_longest(list1, list2).

Use for when iterating over a known collection or range. Use while when the loop condition depends on state that changes unpredictably - waiting for user input, retrying until success, reading until EOF. Most Python loops are for loops because Python has strong iteration support. A while True with a break is idiomatic for infinite loops with an internal exit condition.