Skip to content

Python Loops & Iteration

A loop runs a block of code repeatedly. Python gives you two loop statements, for and while, plus a set of helpers (range, enumerate, zip) and expressions (comprehensions, map, filter, lambda) that make iteration concise.

ToolUse it toExample
forVisit every item of an iterablefor x in items:
whileRepeat until a condition becomes falsewhile count > 0:
breakExit the loop immediatelyif found: break
continueSkip to the next iterationif skip: continue
for ... elseRun code when the loop finished without breakSearch patterns
range()Generate a sequence of integersrange(1, 5) gives 1, 2, 3, 4
enumerate()Get the index and the item togetherfor i, x in enumerate(items):
zip()Walk several iterables in parallelfor a, b in zip(xs, ys):
ComprehensionBuild a list from an iterable[n * 2 for n in nums]
map() / filter()Transform or select itemslist(map(str, [1, 2]))
lambdaWrite a tiny inline functionlambda x: x * 2

A for loop visits each item of an iterable: a list, tuple, string, dictionary, set, range, file, and so on.

for_loops.py
programming_languages = ['Rust', 'Java', 'Python', 'C++']
for language in programming_languages:
print(language)
for char in 'code':
print(char) # c, o, d, e

By default, a dictionary loop yields the keys. Use .items() to get keys and values together.

dictionary_loops.py
ages = {'Ann': 31, 'Bob': 25}
for name in ages: # Keys
print(name)
for age in ages.values(): # Values
print(age)
for name, age in ages.items(): # Both, unpacked from tuples
print(name, age)

An inner loop runs completely for every iteration of the outer loop. With N outer and M inner items, the body executes N * M times.

nested_loops.py
categories = ['Fruit', 'Vegetable']
foods = ['Apple', 'Carrot', 'Banana']
for category in categories:
for food in foods:
print(category, food) # 6 lines in total
loop_patterns.py
# Accumulator: build up a total
total = 0
for n in [5, 10, 15, 20]:
total += n
print(total) # 50
# Counter: tally items that match a condition
vowels = 0
for letter in 'education':
if letter in 'aeiou':
vowels += 1
print(vowels) # 5
# Throwaway variable: when you only need to repeat something
for _ in range(3):
print('Hello')

A while loop repeats as long as its condition is True. Use it when you do not know in advance how many iterations you need.

while_loops.py
countdown = 3
while countdown > 0:
print(countdown) # 3, 2, 1
countdown -= 1
print('Liftoff!')
guessing_game.py
secret_number = 3
guess = 0
while guess != secret_number:
guess = int(input('Guess the number (1-5): '))
if guess != secret_number:
print('Wrong! Try again.')
print('You got it!')

For input validation and retry logic, loop forever and break when you have what you need.

input_validation.py
while True:
text = input('Enter a whole number: ')
try:
number = int(text)
break # Valid input, leave the loop
except ValueError:
print('Not a valid number, try again.')

StatementEffect
breakEnds the loop immediately and skips any else block
continueAbandons the current iteration and jumps to the next one
elseRuns only if the loop finished without hitting break
passDoes nothing. A placeholder for an empty block
loop_control.py
developers = ['Jess', 'Naomi', 'Tom']
# break: stop at the first match
for developer in developers:
if developer == 'Naomi':
break
print(developer) # Jess
# continue: skip one item and keep going
for developer in developers:
if developer == 'Naomi':
continue
print(developer) # Jess, Tom

The else block of a loop is a clean way to express “search, and do something if nothing was found”.

for_else.py
numbers = [3, 7, 9]
for n in numbers:
if n % 2 == 0:
print('Found an even number')
break
else:
print('No even numbers') # Runs, because there was no break
for_else_nested.py
words = ['sky', 'apple', 'rhythm', 'fly', 'orange']
for word in words:
for letter in word:
if letter.lower() in 'aeiou':
print(f"'{word}' contains the vowel '{letter}'")
break
else:
print(f"'{word}' has no vowels")

break exits only the innermost loop. To leave several levels at once, wrap the loops in a function and return.

exit_nested_loops.py
def contains(matrix, target):
for row in matrix:
for value in row:
if value == target:
return True # Leaves both loops and the function
return False
print(contains([[1, 2], [3, 4]], 3)) # True

range() produces a sequence of integers lazily: it stores only start, stop, and step, so range(1_000_000) uses the same tiny amount of memory as range(3).

CallGenerates
range(stop)0 up to stop - 1
range(start, stop)start up to stop - 1
range(start, stop, step)start up to stop - 1, moving by step

The stop value is always excluded.

range_patterns.py
print(list(range(3))) # [0, 1, 2]
print(list(range(1, 5))) # [1, 2, 3, 4]
print(list(range(2, 11, 2))) # [2, 4, 6, 8, 10]
print(list(range(40, 0, -10))) # [40, 30, 20, 10], a negative step counts down
print(list(range(5, 0))) # [], the default step is +1, so nothing is generated
for num in range(1, 4):
print(num) # 1, 2, 3
range_features.py
r = range(0, 10, 3) # 0, 3, 6, 9
print(len(r)) # 4
print(6 in r) # True, checked arithmetically in O(1) time
print(4 in r) # False
range(2.5) # TypeError: 'float' object cannot be interpreted as an integer
range(0, 10, 0) # ValueError: range() arg 3 must not be zero

Prefer Direct Iteration Over range(len(...))

Section titled “Prefer Direct Iteration Over range(len(...))”
avoid_range_len.py
langs = ['Spanish', 'English', 'Russian']
for i in range(len(langs)): # Works, but clunky
print(langs[i])
for lang in langs: # Preferred when you only need the items
print(lang)
for i, lang in enumerate(langs): # Preferred when you also need the index
print(i, lang)

enumerate() pairs each item with a counter, so you never need a manual index += 1. It yields (index, item) tuples, which you unpack in the loop header.

enumerate_patterns.py
languages = ['Spanish', 'English', 'Russian', 'Chinese']
print(list(enumerate(languages)))
# [(0, 'Spanish'), (1, 'English'), (2, 'Russian'), (3, 'Chinese')]
for index, language in enumerate(languages):
print(f'Index {index}: {language}') # Starts at 0
for index, language in enumerate(languages, start=1):
print(f'Language #{index}: {language}') # Starts at 1

zip() combines several iterables into parallel tuples. It stops as soon as the shortest input runs out.

zip_patterns.py
developers = ['Naomi', 'Dario', 'Jessica', 'Tom']
ids = [1, 2, 3, 4]
print(list(zip(developers, ids)))
# [('Naomi', 1), ('Dario', 2), ('Jessica', 3), ('Tom', 4)]
for name, dev_id in zip(developers, ids):
print(f'Name: {name}, ID: {dev_id}')
print(dict(zip(['a', 'b'], [1, 2]))) # {'a': 1, 'b': 2}
pairs = [('a', 1), ('b', 2)]
letters, numbers = zip(*pairs) # "Unzip": ('a', 'b') and (1, 2)
zip_unequal.py
from itertools import zip_longest
names = ['Ann', 'Bob', 'Cy']
ages = [31, 25]
print(list(zip(names, ages))) # [('Ann', 31), ('Bob', 25)], Cy is silently dropped
print(list(zip_longest(names, ages))) # [('Ann', 31), ('Bob', 25), ('Cy', None)]
print(list(zip(names, ages, strict=True))) # ValueError: zip() argument 2 is shorter than argument 1

zip, map, filter, and enumerate return iterators, which can be traversed only once.

iterator_exhaustion.py
pairs = zip([1, 2], [3, 4])
print(list(pairs)) # [(1, 3), (2, 4)]
print(list(pairs)) # [], already consumed

A comprehension builds a list from an iterable in one expression. The full pattern catalogue is in the Python Lists guide, so this section covers the essentials.

list_comprehensions.py
# Filter: keep matching items (the `if` goes at the end)
even_numbers = [num for num in range(21) if num % 2 == 0]
print(even_numbers) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# Choose a value: if/else goes at the front
numbers = [1, 2, 3, 4, 5]
result = [(num, 'Even') if num % 2 == 0 else (num, 'Odd') for num in numbers]
# [(1, 'Odd'), (2, 'Even'), (3, 'Odd'), (4, 'Even'), (5, 'Odd')]
# Related forms
squares = {n: n ** 2 for n in range(3)} # Dict comprehension: {0: 0, 1: 1, 2: 4}
unique_letters = {c for c in 'hello'} # Set comprehension: {'h', 'e', 'l', 'o'}
total = sum(n * n for n in range(5)) # Generator expression: 30, builds no list

FunctionPurposeReturns
filter(predicate, iterable)Keeps items for which the predicate is truthyA filter iterator
map(function, iterable)Applies a function to every itemA map iterator
sum(iterable, start=0)Adds up numbersA number
min(), max()Smallest or largest itemOne item
any(iterable)True if at least one item is truthyA Boolean
all(iterable)True if every item is truthyA Boolean
functional_utilities.py
# filter(): select
words = ['tree', 'sky', 'mountain', 'river', 'cloud', 'sun']
def is_long_word(word):
return len(word) > 4
long_words = list(filter(is_long_word, words))
print(long_words) # ['mountain', 'river', 'cloud']
# map(): transform
celsius = [0, 10, 20, 30, 40]
def to_fahrenheit(temp):
return (temp * 9 / 5) + 32
print(list(map(to_fahrenheit, celsius))) # [32.0, 50.0, 68.0, 86.0, 104.0]
print(list(map(str, [1, 2, 3]))) # ['1', '2', '3']
# sum(): aggregate
numbers = [5, 10, 15, 20]
print(sum(numbers)) # 50
print(sum(numbers, 10)) # 60, the start value is added too
# any() / all(): test
print(any(n > 15 for n in numbers)) # True
print(all(n > 15 for n in numbers)) # False
numbers = [1, 2, 3, 4, 5]
evens = [n for n in numbers if n % 2 == 0] # [2, 4]

Usually the most readable choice.


A lambda is a small, anonymous function written inline: lambda parameters: expression. It can take any number of parameters but contains a single expression, whose value is returned automatically.

lambda_usage.py
numbers = [1, 2, 3, 4, 5]
print((lambda x: x + 1)(5)) # 6, defined and called at once
print(list(filter(lambda x: x % 2 == 0, numbers))) # [2, 4]
people = [('Ann', 31), ('Bob', 25)]
print(sorted(people, key=lambda p: p[1])) # [('Bob', 25), ('Ann', 31)]

1. Do not assign a lambda to a name. If a function needs a name, use def. PEP 8 recommends this, and named functions show up properly in error tracebacks (a lambda’s name is just <lambda>).

lambda_assignment.py
square = lambda x: x ** 2 # Discouraged
def square(num): # Preferred
return num ** 2

2. Keep lambdas simple. Branching or multi-step logic belongs in a regular function.

lambda_complexity.py
# Discouraged: hard to read
result = (lambda x: (x**2 + 2*x - 1) if x > 0 else (x**3 - x + 4))(3)
# Preferred
def calculate_expression(x):
if x > 0:
return x**2 + 2*x - 1
return x**3 - x + 4
print(calculate_expression(3)) # 14

A loop’s total cost is the number of iterations multiplied by the cost of its body. The helpers themselves are cheap.

ToolTimeMemoryNote
for / whileO(n) iterationsO(1) extraCost depends on the loop body
range(...)O(1) to createO(1)x in range(...) is also O(1)
list(range(...))O(n)O(n)Builds every integer in memory
enumerate(), zip()O(n)O(1) extraLazy, items are produced on demand
List comprehensionO(n)O(n)Builds the whole list
Generator expressionO(n)O(1)Produces one item at a time
Nested loopsO(N * M)O(1) extraCost multiplies with each level

  1. Do you have a collection or a known number of repeats? Use a for loop, with range() when you just need to count.

  2. Do you need to repeat until something changes? Use a while loop, and make sure the condition can become false.

  3. Do you need the position as well as the item? Use enumerate() instead of range(len(...)).

  4. Are you reading two or more sequences together? Use zip(), with strict=True if the lengths must match.

  5. Are you building a new list? Use a comprehension. Fall back to a plain loop when the logic gets complicated.


MistakeProblemFix
Forgetting to update the while conditionInfinite loopChange the variable inside the loop, or break
Adding or removing list items while looping over itItems get skippedLoop over a copy or build a new list with a comprehension
for i in range(len(items))Clunky and error-pronefor item in items or enumerate(items)
range(1, 10) expecting 10 to be includedstop is excludedrange(1, 11)
break in a nested loop expecting a full exitOnly the inner loop endsUse a function with return, or a flag
Reusing a zip, map, or filter resultIterators are exhausted after one passConvert to a list first
print(map(...))Shows a map object, not valuesprint(list(map(...)))
zip() on lists of different lengthsExtra items are silently droppedstrict=True or zip_longest
Using the loop variable after the loopIt keeps its last value, or is undefined if the loop never ranDo not rely on it, or initialize it before the loop
Assigning a lambda to a variableViolates PEP 8, poor tracebacksUse def
  1. Loop directly over items, and use enumerate() when you need the index.
  2. Use zip() for parallel iteration, and add strict=True when lengths must match.
  3. Use break and for ... else for searches, and wrap nested searches in a function.
  4. Guarantee that every while loop can end.
  5. Prefer comprehensions to map() and filter() for simple transformations.
  6. Never modify a list while iterating over it.
  7. Keep lambdas to one short expression, and use def for everything else.
  8. Convert iterators with list() when you need to reuse or print the results.