For loops
A for loop in Python iterates over any iterable: a list, string, tuple, dict, set, range, file, or any object that implements __iter__. There is no index by default. If you need one, use enumerate().
1# Iterating a list2fruits = ["apple", "banana", "cherry"]3for fruit in fruits:4 print(fruit)56# Iterating a string7for char in "Python":8 print(char, end=" ") # P y t h o n9print()1011# Iterating a dict, gives keys by default12scores = {"Alice": 92, "Bob": 78}13for name in scores:14 print(name, scores[name])
range()
range() generates integers on demand. It does not build a list, it produces one number at a time. Use it when you need to repeat something N times or loop over numeric indices.
1# range(stop), 0 up to stop-12for i in range(5):3 print(i, end=" ") # 0 1 2 3 44print()56# range(start, stop), start up to stop-17for i in range(2, 8):8 print(i, end=" ") # 2 3 4 5 6 79print()1011# range(start, stop, step)12for i in range(0, 20, 5):13 print(i, end=" ") # 0 5 10 1514print()1516# Countdown17for i in range(5, 0, -1):18 print(i, end=" ") # 5 4 3 2 119print()
break and continue
1# break, exit the loop immediately2for n in range(10):3 if n == 5:4 break5 print(n, end=" ") # 0 1 2 3 46print()78# continue, skip the rest of this iteration9for n in range(10):10 if n % 2 == 0:11 continue12 print(n, end=" ") # 1 3 5 7 913print()
for / else
A for loop can have an else clause. It runs if and only if the loop completed without hitting a break. This is the cleanest way to express the "search and report if not found" pattern.
1def find_prime(numbers):2 for n in numbers:3 if all(n % i != 0 for i in range(2, n)):4 print(f"First prime: {n}")5 break6 else:7 print("No primes found")89find_prime([4, 6, 8, 7, 10]) # First prime: 710find_prime([4, 6, 8, 10]) # No primes found
While loops
A while loop runs as long as its condition is true. Use it when you do not know how many iterations you need: user input, polling a state, reading until a sentinel value. When you know the count or have a sequence, a for loop is almost always cleaner.
1# Count down2n = 53while n > 0:4 print(n, end=" ")5 n -= 16print() # 5 4 3 2 178# Consume a queue9from collections import deque10queue = deque([1, 2, 3, 4])11while queue:12 item = queue.popleft()13 print(item, end=" ") # 1 2 3 414print()
break and while / else
1# Retry loop with break2import random3random.seed(42)45attempts = 06while True:7 attempts += 18 value = random.randint(1, 10)9 if value == 7:10 print(f"Found 7 after {attempts} attempts")11 break1213# while / else, else runs only if no break occurred14n = 1015while n > 0:16 if n == 3:17 print("stopped at 3")18 break19 n -= 120else:21 print("counted all the way down") # not printed here
Infinite loops with controlled exit
while True with a break is the canonical pattern for event loops, REPLs, and retry logic. It is more readable than duplicating the exit condition.
1# Read lines until empty input2lines = []3# In real code: line = input("Enter text (blank to stop): ")4# Simulated here:5inputs = ["hello", "world", "", "ignored"]6idx = 07while True:8 line = inputs[idx]; idx += 19 if not line:10 break11 lines.append(line)1213print(lines) # ['hello', 'world']1415# Exponential backoff retry16import time1718def fetch_with_retry(url, max_retries=3):19 delay = 120 for attempt in range(max_retries):21 try:22 # result = requests.get(url)23 return "success"24 except Exception:25 if attempt == max_retries - 1:26 raise27 time.sleep(delay)28 delay *= 2
enumerate and zip
Two built-in functions that make loops cleaner. enumerate() gives you both the index and value without manual counter management. zip() pairs items from two or more iterables.
enumerate()
1fruits = ["apple", "banana", "cherry"]23# Without enumerate, error-prone counter4for i in range(len(fruits)):5 print(i, fruits[i])67# With enumerate, cleaner and safer8for i, fruit in enumerate(fruits):9 print(i, fruit)1011# Start index at 112for i, fruit in enumerate(fruits, start=1):13 print(f"{i}. {fruit}")
zip()
zip() stops at the shortest iterable. If you need to continue to the longest and fill missing values with a default, use itertools.zip_longest().
1names = ["Alice", "Bob", "Carol"]2scores = [92, 78, 85]3grades = ["A", "C+", "B"]45# Pair two lists6for name, score in zip(names, scores):7 print(f"{name}: {score}")89# Three lists at once10for name, score, grade in zip(names, scores, grades):11 print(f"{name}: {score} ({grade})")1213# zip stops at the shortest14short = [1, 2]15long = [10, 20, 30, 40]16print(list(zip(short, long))) # [(1, 10), (2, 20)]1718# zip_longest fills missing values19from itertools import zip_longest20print(list(zip_longest(short, long, fillvalue=0)))21# [(1, 10), (2, 20), (0, 30), (0, 40)]
Combining enumerate and zip
1fruits = ["apple", "banana", "cherry", "date"]2prices = [1.20, 0.50, 3.00, 4.50]34for i, (fruit, price) in enumerate(zip(fruits, prices), start=1):5 print(f"{i}. {fruit}, {price:.2f}")
See every iteration pattern side by side below.
| iter | fruit |
|---|---|
| 0 | apple |
| 1 | banana |
| 2 | cherry |
| 3 | date |
| 4 | elderberry |
Comprehensions
Comprehensions are compact loop expressions that produce a collection. Python has three kinds: list, dict, and set. All follow the same structure: expression first, then the loop, then an optional filter.
1numbers = range(10)23# List comprehension: [expression for item in iterable if condition]4evens = [x for x in numbers if x % 2 == 0]5squares = [x ** 2 for x in numbers]6print(evens) # [0, 2, 4, 6, 8]7print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]89# Dict comprehension: {key: value for item in iterable}10sq_map = {x: x ** 2 for x in range(6)}11print(sq_map) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}1213# Set comprehension: {expression for item in iterable}14words = ["hello", "world", "hello", "python"]15lengths = {len(w) for w in words}16print(lengths) # {5, 6}
Nested comprehensions
Multiple for clauses are written in the same order as nested loops: outer first, inner second.
1# Flatten a 2D list2matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]3flat = [x for row in matrix for x in row]4print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]56# Cartesian product7sizes = ["S", "M", "L"]8colors = ["red", "blue"]9variants = [(s, c) for s in sizes for c in colors]10print(variants)
When not to use a comprehension
Comprehensions lose their readability advantage as complexity grows. If the expression or condition is long, or if you need side effects, a plain loop communicates intent more clearly.
1# Too complex, use a plain loop2result = [3 transform(item)4 for item in get_items()5 if item.is_valid()6 if item.price < threshold7]89# Clearer as a loop10result = []11for item in get_items():12 if not item.is_valid():13 continue14 if item.price >= threshold:15 continue16 result.append(transform(item))
If it does not fit on one readable line, write a loop. Fewer lines is not the goal. Readable code is.
Generators
A list comprehension builds the entire list in memory before you start using it. A generator expression looks the same but produces values one at a time, only when asked. Swap the square brackets for parentheses and you have a generator. No list is ever built.
1# List comprehension: all 1M items created immediately2squares_list = [x ** 2 for x in range(1_000_000)]34# Generator expression: nothing computed yet5squares_gen = (x ** 2 for x in range(1_000_000))67import sys8print(sys.getsizeof(squares_list)) # ~8 MB9print(sys.getsizeof(squares_gen)) # 104 bytes
Consuming a generator
Generators are lazy, they only compute the next value when you ask for it. You can pass them directly to sum(), list(), max(), or any function that accepts an iterable. They can only be consumed once.
1gen = (x ** 2 for x in range(5))23# Iterate directly4for v in gen:5 print(v, end=" ") # 0 1 4 9 166print()78# Useful with built-ins, no intermediate list needed9total = sum(x ** 2 for x in range(1000))10print(total) # 3328335001112biggest = max(len(word) for word in ["hello", "world", "python"])13print(biggest) # 61415# Generators are exhausted after one pass16gen = (x for x in range(3))17print(list(gen)) # [0, 1, 2]18print(list(gen)) # [], exhausted
Generator functions with yield
A function with yield is a generator function. Calling it returns a generator object without running any code. Each next() call runs until the next yield, then pauses with its state intact.
1def fibonacci():2 a, b = 0, 13 while True:4 yield a5 a, b = b, a + b67fib = fibonacci()8print([next(fib) for _ in range(10)])9# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]1011# Reading a large file line by line, never loads the whole file12def read_lines(path):13 with open(path) as f:14 for line in f:15 yield line.rstrip()
Compare eager vs lazy evaluation below. In generator mode, each next() computes exactly one value.
A list comprehension evaluates all items immediately. All 8 values are computed at once and stored in memory.
Loop patterns
Accumulate
Start with an identity value (0 for sum, 1 for product, [] for a list) and update it on every iteration. Built-ins like sum(), min(), and max() handle the common cases.
1nums = [3, 1, 4, 1, 5, 9, 2, 6]23# Use built-ins when you can4total = sum(nums)5biggest = max(nums)6print(total, biggest) # 31 978# Manual accumulation, running product9product = 110for n in nums:11 product *= n12print(product) # 64801314# Collect into a structure15evens = []16for n in nums:17 if n % 2 == 0:18 evens.append(n)19print(evens) # [4, 2, 6]
Find first
Return as soon as you find what you need. Continuing to iterate after finding the answer wastes time. Use next() with a generator for a one-liner version.
1nums = [4, 7, 2, 9, 1, 5]23# Loop version4def first_odd(items):5 for n in items:6 if n % 2 != 0:7 return n8 return None910print(first_odd(nums)) # 71112# One-liner with next()13first = next((n for n in nums if n % 2 != 0), None)14print(first) # 7
Sliding window
1def windows(seq, size):2 for i in range(len(seq) - size + 1):3 yield seq[i : i + size]45data = [1, 2, 3, 4, 5]6for w in windows(data, 3):7 print(w) # [1,2,3] [2,3,4] [3,4,5]
Chunking
1def chunks(lst, size):2 for i in range(0, len(lst), size):3 yield lst[i : i + size]45data = list(range(10))6for chunk in chunks(data, 3):7 print(chunk)8# [0,1,2] [3,4,5] [6,7,8] [9]
Pairwise iteration
1from itertools import pairwise # Python 3.10+23temps = [20, 23, 19, 25, 22]4for prev, curr in pairwise(temps):5 change = curr - prev6 sign = "+" if change > 0 else ""7 print(f"{prev} -> {curr} ({sign}{change})")