Python Loops and Iteration Control: For, While, Else & Iterators
Master Python iteration. Learn about for loops, range parameters, while loops, loop control (break, continue, pass), the unique loop else block, enumerate, zip, and the Iterator Protocol.
Table of Contents
- Introduction
- Learning Objectives
- Prerequisites
- The For Loop: Iterating Over Collections
- The Range Object: Lazy Evaluation & Memory Efficiency
- The While Loop: Conditional & Sentinel Iteration
- Loop Control Statements: Break, Continue, Pass
- The Loop Else Block: A Unique Python Feature
- Advanced Iteration Utilities: Enumerate, Zip, Reversed
- Under the Hood: The Iterator Protocol
- Visual Loop Control Flow Diagrams
- Real-World and Production Examples
- Best Practices & Common Mistakes
- Performance & Security Notes
- Interview Insights
- Frequently Asked Questions (FAQs)
- Summary
- Related Tutorials
Introduction
In programming, iteration is the process of executing a set of instructions repeatedly. When we need to process lists of data, query databases row-by-row, or execute a polling task until a condition is satisfied, we rely on loops.
Python offers two primary loop constructs: the for loop and the while loop.
Unlike languages where for loops are index-based counters (e.g., for (int i=0; i<n; i++)), Python's for loops are iterable-driven. They act as an iterator-based engine that automatically extracts items one-by-one from objects.
Underneath this interface lies Python's Iterator Protocol, which powers list comprehensions, generator expressions, and custom collections. Additionally, Python features a loop-level else clause and built-in helper functions like enumerate() and zip().
This guide covers iteration in Python, from basic loops to advanced iteration patterns and the underlying protocol details.
Learning Objectives
By the end of this tutorial, you will be able to:
- Trace and implement
forandwhileloops for complex traversal tasks. - Explain the memory efficiency of the
range()sequence generator. - Implement loop control structures using
break,continue, andpass. - Apply Python's loop-level
elseblock to write clean search patterns. - Traversal multiple datasets in parallel using
zip()and unpack index-value pairs withenumerate(). - Construct custom iterable classes by implementing
__iter__()and__next__()(The Iterator Protocol). - Refactor loops that modify collections during iteration to avoid data corruption.
Prerequisites
Before starting this tutorial, make sure you understand:
- Python Variables & Data Types — specifically sequences (lists, strings, tuples) and collections.
- Python Conditional Statements — branching logic and truth values.
The For Loop: Iterating Over Collections
The for loop in Python traverses elements of a sequence or any iterable object in the order they appear.
# Traversal of a list
cities = ["Tokyo", "New York", "London"]
for city in cities:
print(f"Destination: {city}")
Traversal of Dictionaries
When iterating over a dictionary, you can traverse keys, values, or key-value pairs (tuples) using its built-in methods:
user_roles = {"Alice": "Admin", "Bob": "Moderator", "Charlie": "User"}
# 1. Iterating over keys (Default behavior)
for user in user_roles:
print(f"Username: {user}")
# 2. Iterating over values
for role in user_roles.values():
print(f"Role: {role}")
# 3. Iterating over key-value pairs (Unpacking items)
for user, role in user_roles.items():
print(f"{user} has access level: {role}")
The Range Object: Lazy Evaluation & Memory Efficiency
The built-in range() function generates an immutable sequence of numbers. It is commonly used to run a loop a set number of times.
Syntax Variations
# range(stop) - Generates numbers from 0 up to, but not including, stop
for i in range(3):
print(i) # Prints: 0, 1, 2
# range(start, stop) - Starts at 'start', stops before 'stop'
for i in range(5, 8):
print(i) # Prints: 5, 6, 7
# range(start, stop, step) - Steps forward (or backward) by 'step'
for i in range(10, 20, 3):
print(i) # Prints: 10, 13, 16, 19
Lazy Evaluation Mechanism
In Python 3, range() does not allocate memory for a list of all numbers in the specified range. Instead, it returns a lazy sequence object of type <class 'range'>. This object calculates numbers on-demand as they are requested.
# Generating a range of 10 million integers
huge_range = range(10_000_000)
# Memory footprint is negligible, as it only stores start, stop, and step properties.
import sys
print(f"Memory size: {sys.getsizeof(huge_range)} bytes") # Output: 48 bytes!
The While Loop: Conditional & Sentinel Iteration
A while loop repeatedly executes a block of code as long as its condition remains truthy. It is typically used when the number of iterations is not known beforehand.
count = 1
while count <= 3:
print(f"Iteration: {count}")
count += 1
Infinite Loops and Sentinel Values
If the loop's condition never evaluates to a falsy value, the loop runs indefinitely. This can crash applications or consume excessive CPU resources. To prevent this, we use sentinel values or control variables to terminate the loop:
# Sentinel-controlled loop
user_input = ""
while user_input.lower() != "exit":
user_input = input("Enter command (or type 'exit' to quit): ")
print(f"Executing: {user_input}")
Loop Control Statements: Break, Continue, Pass
You can modify a loop's normal execution flow using control statements.
1. break
Terminates the loop immediately. Program control moves to the statement following the loop body.
for num in range(1, 10):
if num == 5:
break # Exit loop immediately when num is 5
print(num) # Prints: 1, 2, 3, 4
2. continue
Skips the remaining statements in the current iteration and jumps directly to the next loop pass.
for num in range(1, 6):
if num == 3:
continue # Skip printing 3
print(num) # Prints: 1, 2, 4, 5
3. pass
A null statement used as a placeholder. It does not affect execution flow and is used when syntax requires a statement but no action is needed.
for num in range(5):
if num == 2:
pass # TODO: Handle this specific condition later
print(num)
The Loop Else Block: A Unique Python Feature
Python allows you to add an else block to both for and while loops.
> [!IMPORTANT]
> The else block executes only if the loop completes all iterations successfully without encountering a break statement.
def find_prime(numbers):
for num in numbers:
if num % 2 == 0:
print(f"Found an even number: {num}")
break # Triggers break, else block will be skipped
else:
# Executes only if the loop finishes without breaking
print("No even numbers found in the entire dataset.")
find_prime([1, 3, 5]) # Output: No even numbers found in the entire dataset.
find_prime([1, 4, 5]) # Output: Found an even number: 4
Advanced Iteration Utilities: Enumerate, Zip, Reversed
1. enumerate()
Avoid creating manual counter variables. enumerate() yields pairs containing the index and the item from an iterable:
cart = ["Laptop", "Mouse", "Monitor"]
for index, item in enumerate(cart, start=1):
print(f"Item #{index}: {item}")
# Output:
# Item #1: Laptop
# Item #2: Mouse
# Item #3: Monitor
2. zip()
Iterates over multiple iterables in parallel, yielding tuples of matched elements:
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name} scored {score}")
Note: By default, zip() stops when the shortest iterable is exhausted. In Python 3.10+, you can pass strict=True to raise a ValueError if the iterables are of unequal length.
3. reversed()
Traverses a sequence in reverse order without modifying the original collection:
for item in reversed(["low", "medium", "high"]):
print(item) # Prints: high, medium, low
Under the Hood: The Iterator Protocol
When you write for item in obj:, Python does not simply index the object. Instead, it invokes the Iterator Protocol.
sequenceDiagram
participant Loop as Python Runtime Loop
participant Obj as Iterable Object
participant Iter as Iterator Object
Loop->>Obj: __iter__()
Obj-->>Iter: Returns Iterator instance
loop Each Iteration
Loop->>Iter: __next__()
alt Has Next Item
Iter-->>Loop: Yields element value
else Sequence Exhausted
Iter-->>Loop: Raises StopIteration Error
end
end
Note over Loop: Loop catches StopIteration<br/>and exits gracefully.
The Protocol Mechanics
- Python calls
iter(obj), which invokes the object's__iter__()method. This method must return an iterator object. - Python then repeatedly calls
next(iterator), which invokes the iterator's__next__()method. - Once there are no more elements,
__next__()raises aStopIterationexception. The loop catches this exception and exits.
Implementing a Custom Iterator
You can make any custom class iterable by defining both __iter__() and __next__() methods:
class Countdown:
"""An iterator that counts down from a given value."""
def __init__(self, start: int):
self.current = start
def __iter__(self):
# An iterator must return itself from __iter__
return self
def __next__(self) -> int:
if self.current <= 0:
raise StopIteration
val = self.current
self.current -= 1
return val
# Test custom iterator
counter = Countdown(3)
for num in counter:
print(num)
# Output:
# 3
# 2
# 1
Visual Loop Control Flow Diagrams
The flowchart below shows the execution paths of loops containing continue, break, and else blocks.
graph TD
A[Start Loop Iteration] --> B{Are there items left?}
B -- Yes --> C[Retrieve Next Item]
C --> D{Is Continue Condition Met?}
D -- Yes --> A
D -- No --> E{Is Break Condition Met?}
E -- Yes --> Z[Exit Loop Immediately]
E -- No --> F[Execute Loop Body]
F --> A
B -- No --> G[Execute Loop ELSE Block]
G --> Y[End Loop Execution]
Z --> Y
Real-World and Production Examples
Example 1: Network Request Retry Loop with Exponential Backoff
In network programming, trying to reconnect immediately after a failure can overload the server. A common pattern is using a while loop with exponential backoff and a maximum retry threshold:
import time
import random
def mock_connect_to_database() -> bool:
# Simulates a connection attempt (80% chance of failing)
return random.random() > 0.8
def establish_connection(max_attempts: int = 5) -> bool:
attempt = 1
delay = 1.0 # Initial delay in seconds
while attempt <= max_attempts:
print(f"Connection attempt {attempt}/{max_attempts}...")
if mock_connect_to_database():
print("Successfully connected to database!")
return True
print(f"Connection failed. Retrying in {delay} seconds...")
time.sleep(delay)
# Exponential backoff math: Double the delay on each failure
delay *= 2
attempt += 1
else:
# Runs only if max_attempts is exceeded without connecting
print("Error: Maximum retry limit reached. Failed to connect.")
return False
# Execute connection attempt
establish_connection()
Example 2: Safe Data Batching Generator
When processing large datasets (e.g., matching database records or training machine learning models), reading all records into memory at once can cause Out-Of-Memory (OOM) errors. We can use a generator loop to batch data:
from typing import List, Generator
def batch_data_stream(data: List[int], batch_size: int) -> Generator[List[int], None, None]:
"""
Yields data elements grouped into batches of size 'batch_size'.
"""
for i in range(0, len(data), batch_size):
yield data[i : i + batch_size]
# Simulate a large data stream
large_dataset = list(range(100, 115))
# Process stream in batches of 5 elements
for batch in batch_data_stream(large_dataset, 5):
print(f"Processing batch block: {batch}")
# Output:
# Processing batch block: [100, 101, 102, 103, 104]
# Processing batch block: [105, 106, 107, 108, 109]
# Processing batch block: [110, 111, 112, 113, 114]
Best Practices & Common Mistakes
Best Practices
- Prefer
enumerate()Overrange(len(seq)): Accessing elements by index usingseq[i]is less readable than usingenumerate():# Avoid for i in range(len(items)): print(i, items[i]) # Preferred for i, item in enumerate(items): print(i, item) - Avoid Modifying Collections During Iteration: Modifying a list or dictionary while looping over it can cause skipped items or runtime errors. To modify a collection safely, iterate over a copy of it or create a new collection:
# Dangerous - may skip elements for item in my_list: if condition(item): my_list.remove(item) # Safe - iterates over a slice copy for item in my_list[:]: if condition(item): my_list.remove(item)
Common Mistakes
- Assuming
range()Returns a List: In Python 3,range()is a sequence object, not a list. To get a list representation of a range, cast it explicitly:list(range(5)). - Forgetting Loop Counter Updates: In
whileloops, failing to update the loop condition variable (e.g., omittingi += 1) creates an infinite loop.
Performance & Security Notes
List Comprehensions vs. Traditional Loops
For simple data transformations, List Comprehensions run faster than traditional for loops because the iteration is optimized at C-speed inside the CPython interpreter:
# Traditional Loop
squares = []
for x in range(1000):
squares.append(x * x)
# List Comprehension (Faster and more concise)
squares = [x * x for x in range(1000)]
Security: Maximum Iteration Caps (DoS Prevention)
When processing external inputs (e.g., XML files or API pagination requests) in a while loop, always enforce a maximum limit on iterations. This prevents malicious payloads from causing infinite loops and Denial of Service (DoS) conditions:
# Unsafe loop - can run indefinitely if API response always returns a next page token
while next_page_token:
next_page_token = fetch_page(next_page_token)
# Safe loop - caps maximum iterations
max_pages = 500
pages_fetched = 0
while next_page_token and pages_fetched < max_pages:
next_page_token = fetch_page(next_page_token)
pages_fetched += 1
Interview Insights
Typical Interview Questions:
-
How does the
elseclause work in Python loops? Answer Key: Theelseblock runs only if the loop completes all iterations without encountering abreakstatement. If abreakis triggered, theelseblock is skipped. -
What is the difference between an Iterable and an Iterator? Answer Key: An Iterable is any object that can return an iterator via its
__iter__()method (e.g., list, set, dict). An Iterator is an object with state that returns the next element via__next__()and raisesStopIterationwhen finished. All iterators must also be iterables. -
Why is
range(1000000)memory-efficient in Python 3? Answer Key:range()returns a lazy sequence object of type<class 'range'>. It does not store the full sequence of numbers in memory. Instead, it stores only the start, stop, and step values, calculating each number on-the-fly as it is requested. -
How do you iterate over two lists in parallel? Answer Key: Use the built-in
zip()function. It yields tuples matching elements from each list. In Python 3.10+, passstrict=Trueto raise aValueErrorif the lists have different lengths.
Frequently Asked Questions (FAQs)
Q: Can a while loop be faster than a for loop in Python?
A: Generally, no. Python's for loop executes its iteration steps in optimized C code within the interpreter, whereas while loops rely on evaluating a Python expression on each pass, which is typically slower.
Q: How can I change the direction of iteration inside a for loop?
A: Use the reversed() built-in function to iterate backward, or use a negative step value in range() (e.g., range(10, 0, -1)).
Summary
Python loops are designed to iterate over collections directly. The for loop uses the Iterator Protocol, which can be implemented in custom classes using __iter__ and __next__. The else block provides a clean fallback path for search operations, and lazy evaluation in range() keeps memory usage low.
Related Tutorials
- Python Functions and Return Dynamics
- Working with Lists and Tuples
- Data Structures: Dictionaries & Sets
Technical SEO Schema Metadata
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Article",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://vsnexos.com/placement-prep/python/for-loops"
},
"headline": "Python Loops and Iteration Control: For, While, Else & Iterators",
"description": "Master Python loops and iterators. Explore range memory footprints, control statements, loop else, parallel zip, and the Iterator Protocol.",
"image": "https://vsnexos.com/images/courses/python-loops.png",
"author": {
"@type": "Organization",
"name": "Vsnexos"
},
"publisher": {
"@type": "Organization",
"name": "Vsnexos",
"logo": {
"@type": "ImageObject",
"url": "https://vsnexos.com/images/logo.png"
}
}
},
{
"@type": "Course",
"name": "Python Zero to Hero Master Course",
"description": "Go from absolute beginner to industry-ready software developer in Python.",
"provider": {
"@type": "Organization",
"name": "Vsnexos",
"sameAs": "https://vsnexos.com"
}
},
{
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://vsnexos.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Placement Prep",
"item": "https://vsnexos.com/placement-prep"
},
{
"@type": "ListItem",
"position": 3,
"name": "Python Course",
"item": "https://vsnexos.com/placement-prep/python"
},
{
"@type": "ListItem",
"position": 4,
"name": "Loops and Iterators",
"item": "https://vsnexos.com/placement-prep/python/for-loops"
}
]
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How does loop else work in Python?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The else block in a Python loop executes only if the loop runs to completion without hitting a break statement."
}
},
{
"@type": "Question",
"name": "What is the Iterator Protocol in Python?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The Iterator Protocol consists of __iter__() returning an iterator object, and __next__() returning the next element or raising StopIteration."
}
},
{
"@type": "Question",
"name": "How do you loop over two lists in parallel?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Use the zip() built-in function to iterate over two lists in parallel, returning matched pairs as tuples."
}
}
]
}
]
}