Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›for Loops

Control Flow

for Loops

The for loop is a fundamental iteration construct designed to traverse over elements within a collection sequentially. By abstracting away manual index management, it allows for more readable and robust code when processing data sets. You should utilize this construct whenever you need to perform an operation on every item contained within an iterable object.

The Mechanics of Iteration

At its core, a Python for loop functions by interacting with an object's internal iterator protocol. When you define a loop, Python calls the __iter__() method on the collection, obtaining an iterator object. It then repeatedly calls next() on that iterator to retrieve the next item, assigning it to the target variable before executing the block of code inside the loop. This process continues automatically until the iterator raises a StopIteration exception, which signals that no further elements remain. Understanding that the loop interacts with the iterator—not necessarily the original container—is crucial for writing performant code. This mechanism ensures that the loop is efficient and memory-conscious, as it fetches elements one at a time rather than loading an entire sequence into memory if a generator is used.

# Iterating over a list of server health statuses
server_statuses = ['online', 'offline', 'maintenance', 'online']

for status in server_statuses:
    # The loop fetches each string one by one until exhausted
    print(f"Current server status: {status}")

Iterating Over Numeric Sequences

While it may seem intuitive to think of loops as counting numbers, the range() function actually generates a sequence object that produces integers lazily. When you pass a range into a for loop, the loop consumes this sequence until the specified endpoint is reached. This is conceptually different from languages where you manually increment an index variable because the range handles its own internal state tracking. By using range(), you can control the start, stop, and step of your iteration, allowing for precise execution patterns. Because the object is lazy, creating a range of one million numbers does not consume significant memory; it merely calculates the next integer on demand. This makes range() the standard tool for cases where an action must be repeated a specific number of times.

# Repeating a data collection task 5 times
for i in range(5):
    # i starts at 0 and ends at 4
    print(f"Attempt {i + 1}: Connecting to database...")

Traversing Dictionaries

Dictionaries in Python are collections of key-value pairs, and iterating over them requires understanding that the default behavior targets the keys. When you perform a loop directly on a dictionary, Python yields each unique key in the order they were inserted. However, to access the associated data, you frequently need both the key and the value simultaneously. The items() method solves this by returning a view object that yields tuples containing both elements. Using tuple unpacking within the loop header allows you to assign these values to separate, descriptive variables in one clean step. This approach is superior to looking up keys inside the loop using square brackets, as it reduces the number of hashing operations required, leading to cleaner and more efficient code execution.

# Processing user configuration settings
user_config = {'theme': 'dark', 'notifications': True, 'volume': 85}

for setting, value in user_config.items():
    # Unpacking key and value directly in the loop
    print(f"Setting {setting} is currently set to: {value}")

Modifying Iteration with enumerate

Often, you need to track the position of an item while simultaneously accessing its value. While a beginner might use a counter variable and increment it manually, the enumerate() function provides an idiomatic and safer alternative. This function wraps an iterable and yields pairs consisting of the current index and the corresponding element. By keeping the counting logic internal to Python, you eliminate the possibility of off-by-one errors or forgetting to increment the counter within complex loop logic. This is particularly useful when you need to update a specific index in a list or print formatted logs that require item numbering. Because enumerate accepts a start argument, you can even begin your indexing at a number other than zero, further increasing its versatility.

# Adding ranks to a list of competition finalists
finalists = ['Alice', 'Bob', 'Charlie']

for rank, name in enumerate(finalists, start=1):
    # enumerate handles the index tracking for us
    print(f"Rank {rank}: {name}")

Control Flow within Loops

Inside a loop, you can further refine execution using the break and continue statements. The break statement immediately terminates the entire loop, jumping to the first line of code following the loop block. This is vital for search operations where you find the target and no longer need to process remaining elements. Conversely, the continue statement skips the remainder of the current iteration and forces the loop to move immediately to the next item. Additionally, Python provides a unique else clause for loops, which executes only if the loop finishes normally without encountering a break statement. This is an advanced feature that allows for cleaner logic when checking if a search condition was never met during the entire cycle of iteration.

# Filtering out restricted file types
files = ['script.py', 'image.png', 'notes.txt', 'data.csv']

for file in files:
    if file == 'notes.txt':
        continue  # Skip processing the text file
    if file == 'data.csv':
        break     # Stop entirely when reaching the data file
    print(f"Processing: {file}")

Key points

  • A for loop abstracts the iterator protocol to traverse collections without manual index management.
  • The range() function is a memory-efficient tool for generating numeric sequences for iteration.
  • Dictionary items() method allows simultaneous access to both keys and values during a loop.
  • Using enumerate() is the preferred way to track indices while iterating over elements.
  • The break statement exits the loop immediately, whereas continue skips the current iteration.
  • The optional else clause executes after a loop finishes, provided no break statement was triggered.
  • Iteration targets are assigned sequentially from an iterator until a StopIteration signal is received.
  • Looping directly over an iterable is always more efficient than manually handling indices with range().

Common mistakes

  • Mistake: Modifying the list being iterated over. Why it's wrong: Python's loop pointer can skip elements or cause index errors if items are added or removed. Fix: Iterate over a copy of the list using 'list[:]'.
  • Mistake: Expecting the loop variable to persist outside the loop. Why it's wrong: In Python, loop variables leak into the global or local scope, often causing 'NameError' or confusion when checked after the loop. Fix: Do not rely on loop variables outside their intended scope.
  • Mistake: Using 'range(len(list))' when the actual element is needed. Why it's wrong: It is unpythonic and harder to read than direct iteration. Fix: Use 'for item in list:' directly, or 'enumerate(list)' if you need the index.
  • Mistake: Forgetting that 'range(start, stop)' excludes the stop value. Why it's wrong: Beginners often loop one time too few because they forget the end boundary is exclusive. Fix: Use 'range(start, stop + 1)' if you need to include the final number.
  • Mistake: Using 'range()' without arguments. Why it's wrong: 'range()' is a function that requires at least one argument; calling it empty is a TypeError. Fix: Always provide the stop value, like 'range(10)'.

Interview questions

What is the primary purpose of a for loop in Python, and how does it differ from a while loop?

A for loop in Python is primarily used for iterating over a sequence, such as a list, tuple, dictionary, set, or string. It is designed to execute a block of code once for each element in that sequence. In contrast, a while loop continues to execute as long as a specified boolean condition remains true. You use a for loop when you know the number of iterations or the collection you are traversing beforehand, whereas a while loop is better suited for scenarios where the exit condition depends on dynamic runtime values.

How does the range() function work, and why is it frequently used within for loops?

The range() function in Python generates an immutable sequence of numbers. It is frequently used with for loops because it allows you to repeat an action a specific number of times without needing an existing collection. For example, 'for i in range(5):' will execute the loop body exactly five times, with 'i' taking values from 0 to 4. This is efficient for memory because it generates numbers on the fly rather than creating a full list in memory.

Can you explain what happens when we use the 'enumerate' function during a for loop, and why would you prefer it over accessing indices manually?

The enumerate() function adds a counter to an iterable and returns it as an enumerate object. When used in a for loop like 'for index, value in enumerate(my_list):', it provides both the index and the item simultaneously. This is much cleaner and more Pythonic than using 'range(len(my_list))' to access items by index manually. Using enumerate eliminates the risk of off-by-one errors and makes the code significantly more readable and maintainable by clearly expressing intent.

Compare using a traditional for loop to iterate over a list versus using a list comprehension. When is one approach better than the other?

A traditional for loop is better for complex logic, multiple lines of code, or when you need to perform side effects like printing or logging during iteration. A list comprehension is a concise, declarative way to create a new list from an existing iterable, typically written in a single line: '[x * 2 for x in my_list]'. Comprehensions are often faster and more readable for simple transformations, but if the logic is too complex, they can become difficult to debug.

What are 'break' and 'continue' statements, and how do they alter the control flow of a for loop?

The 'break' and 'continue' statements are control flow tools used to modify loop behavior. A 'break' statement terminates the loop entirely, immediately exiting the code block regardless of how many items remain in the iterable. A 'continue' statement, on the other hand, skips the rest of the code for the current iteration and forces the loop to jump to the very next item in the sequence. These are essential for optimizing performance by avoiding unnecessary checks once a desired result is found.

Explain the concept of an 'else' block attached to a for loop. When does this block execute, and what is its most common use case?

In Python, a for loop can have an 'else' clause that executes only if the loop finishes normally—meaning it iterated through all items without hitting a 'break' statement. This is highly useful for search algorithms where you are looking for a specific item in a collection. You can use the 'else' block to handle the case where the item was not found, which is cleaner than maintaining an external 'found' flag variable that you would otherwise have to track throughout your code.

All Python interview questions →

Check yourself

1. What is the output of: for i in range(1, 4): print(i, end='')

  • A.1234
  • B.123
  • C.0123
  • D.Error
Show answer

B. 123
Range(1, 4) starts at 1 and goes up to, but does not include, 4. 1234 includes the endpoint, 0123 includes the start index 0, and Error is incorrect as range is valid.

2. If you have 'my_list = [10, 20, 30]', what is the best way to iterate over both indices and values?

  • A.for i in range(len(my_list)): print(i, my_list[i])
  • B.for i, val in enumerate(my_list): print(i, val)
  • C.for i in my_list: print(i)
  • D.for i in list(my_list): print(i)
Show answer

B. for i, val in enumerate(my_list): print(i, val)
Enumerate is the standard Pythonic way to get both index and value. Using range(len()) is valid but considered unpythonic, while the others only provide values, not indices.

3. What happens if you use 'break' inside a nested for loop?

  • A.It terminates both the inner and outer loops.
  • B.It stops the program entirely.
  • C.It terminates only the innermost loop where it is called.
  • D.It causes a syntax error.
Show answer

C. It terminates only the innermost loop where it is called.
A break statement only exits the current block of the nearest enclosing loop. It does not stop the outer loop, terminate the program, or cause a syntax error.

4. What is the result of 'for i in 'Py': print(i)'?

  • A.Py
  • B.P y
  • C.Error because strings are not iterable
  • D.Nothing happens
Show answer

B. P y
Strings are iterable in Python, so the loop iterates character by character. 'print' defaults to adding a newline, so P and y are printed on separate lines. 'Py' would require 'end='''.

5. Which of the following will iterate over the list in reverse order?

  • A.for i in my_list.reverse():
  • B.for i in reversed(my_list):
  • C.for i in my_list[::-1]:
  • D.Both the second and third options are correct.
Show answer

D. Both the second and third options are correct.
Both 'reversed()' and slicing '[::-1]' work correctly for iteration. '.reverse()' modifies the list in place and returns None, making it invalid for a 'for' loop expression.

Take the full Python quiz →

← Previousif / elif / else StatementsNext →while Loops

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app