A Complete Guide to Using for Loops in Python

Written by Brendon
8 July 2025

Learn how for loops work in Python, from the basics of iterating over lists and dictionaries to advanced patterns using enumerate(), zip(), chain(), and more.

Spaceman floating towards a looping wormhole in space.

Introduction #

In Python, if you want to repeat an action for each item in a collection—without writing the same line of code over and over—a for loop is your tool of choice.

It’s straightforward, readable, and designed to let you focus on what you’re doing with each item, not how you’re moving through them. Compared to other languages that rely on manual index management, Python’s for loop feels refreshingly high-level.

Here’s the basic form:

for item in iterable:
    # do something with item

That’s it. No counters, no setup, no post-loop cleanup. Python handles the plumbing so you can focus on the logic.


By the way, here's the video version of this article. Perfect if you want a short, high level overview before diving into this more in depth article.


Why Use for Loops? #

Python’s for loop is built for clarity and expressiveness. Some common use cases include:

  • Iterating through a list of items (e.g., user inputs, file lines, search results)
  • Performing actions on each character in a string
  • Looping through the keys and values in a dictionary
  • Applying transformations or filters to data

If it’s iterable, it’s loopable. And in Python, most things are iterable—by design.

A First Example #

Here’s a basic loop over a list:

names = ['Alice', 'Bob', 'Charlie']

for name in names:
    print(f"Hello, {name}")

# Hello, Alice
# Hello, Bob
# Hello, Charlie

It’s clear what’s happening. No index juggling. Just a loop that gives you each item in turn.

A Brief Word on Iterables #

When we say for item in iterable, we mean any object that can return its elements one at a time. That includes lists, tuples, strings, sets, dictionaries, files, and more.

for letter in "Python":
    print(letter)

This will print each character in the string. Behind the scenes, Python is requesting each element from the string’s iterator. But you don’t need to manage that yourself—unless you're doing something more advanced, like writing custom iterable classes.

For now, it’s enough to know that if something “feels” like a sequence, chances are Python lets you loop over it.

For a detailed dive into iterables, see Python Iterators vs Iterables.

Looping Over Basic Iterables #

Python’s for loop works with any iterable—and some of the most common ones are the built-in collection types. Lists, tuples, strings, dictionaries, and sets all support straightforward, readable iteration.

Let’s walk through the essentials.

Lists and Tuples #

Lists and tuples are ordered sequences. Looping over them is exactly what you’d expect:

items = ['apple', 'banana', 'cherry']

for item in items:
    print(item)

# apple
# banana
# cherry

Python hands you each element in order—left to right, no surprises. Tuples work the same way:

coordinates = (4, 7, 9)

for value in coordinates:
    print(value)

The only real difference between lists and tuples in this context is that tuples are immutable. But as far as looping goes, they're interchangeable.

Strings #

Strings are sequences of characters, which makes them iterable too:

for char in "loop":
    print(char)

# l
# o
# o
# p

This can be handy for parsing or transforming text one character at a time. If you’ve ever needed to count vowels, reverse a word, or inspect input, you’ve likely done something like this.

Sets #

Sets are unordered collections of unique items. You can loop over them, but keep in mind that the order of iteration is not guaranteed:

colors = {'red', 'green', 'blue'}

for color in colors:
    print(color)

# green
# blue
# red

Or possibly a different order—don’t use sets if the order matters.

Dictionaries #

Dictionaries require a bit more care, since each item is a key–value pair. You can loop through the keys:

person = {'name': 'Alice', 'age': 30}

for key in person:
    print(key)

But most of the time, you’ll want to access both the key and its value:

for key, value in person.items():
    print(f"{key}: {value}")

If you only need values:

for value in person.values():
    print(value)

Or just the keys (explicitly):

for key in person.keys():
    print(key)

Dictionaries offer flexibility, but make sure you're clear about what you’re looping over—keys, values, or both.

Summary #

Iterable Type Yields Notes
List / Tuple Each item Ordered, common use case
String Each character Also ordered
Set Each item Unordered, items are unique
Dictionary Keys by default Use .items() or .values() as needed

Python makes looping over collections feel natural. In most cases, it’s as simple as writing for x in y. Just be aware of what your iterable is giving you—and how you want to use it.

The range() Function #

When you want to loop a specific number of times—or you need to generate a sequence of numbers—the built-in range() function is your friend. It’s efficient, predictable, and plays nicely with for loops.

You can think of range() as a generator of numbers: it doesn’t produce a list outright, but instead gives you each number when the loop asks for it.

Basic Usage #

At its simplest, range() takes one argument—the stop value—and starts counting from zero:

for i in range(5):
    print(i)

# 0
# 1
# 2
# 3
# 4

Note: the stop value is exclusive, so this prints up to 4, not 5. A small detail, but a common gotcha when you’re just starting out.

Start and Stop #

You can also provide a start value explicitly:

for i in range(2, 6):
    print(i)

# 2
# 3
# 4
# 5

Still exclusive on the upper bound.

Adding a Step #

Want to skip numbers? Use the optional step argument:

for i in range(0, 10, 2):
    print(i)

# 0
# 2
# 4
# 6
# 8

The third argument controls how much to increment each time. This is useful for even/odd iteration, sampling, or any case where you don’t want to visit every single number.

You can also step backward using a negative step:

for i in range(5, 0, -1):
    print(i)

# 5
# 4
# 3
# 2
# 1

Yes, you can count down. No need to reverse a list or do anything fancy.

Looping by Index #

A common use of range() is when you need access to both an index and an item:

items = ['a', 'b', 'c']

for i in range(len(items)):
    print(i, items[i])

This works, but it's not the most Pythonic way—you’ll typically prefer enumerate()for that, which we’ll cover shortly. Still, this pattern is useful when you're dealing with slices, skipping elements, or need more control over the loop.

A Note on Performance #

In Python 3, range() returns a special range object that generates values as needed. It doesn’t create a list in memory (unless you explicitly convert it), which makes it efficient even for large numbers:

# Safe and efficient
for i in range(1_000_000):
    pass

This is why you don’t need to worry about range() being slow or memory-hungry—unless you force it into a list.

Summary #

  • range(stop) → starts from 0
  • range(start, stop) → counts from start to stop - 1
  • range(start, stop, step) → adds a step (positive or negative)
  • Works great with for loops, especially when you need numeric iteration

It’s one of those Python tools that does one thing, does it well, and quietly handles edge cases you didn’t even know were edge cases.

Using enumerate() in Loops #

Sometimes, when you're looping through a sequence, you need to know not just what you're iterating over, but where you are in that sequence. Sure, you could reach for range(len(...)), but Python has a cleaner, more expressive option: enumerate().

It’s built for those situations where the index actually matters—but you’d rather not clutter your code with indexing syntax.

Why Not Just Use range(len(...))? #

Let’s take a quick look:

items = ['a', 'b', 'c']

for i in range(len(items)):
    print(i, items[i])

It works, but it feels a bit low-level. You’re manually juggling indices and elements, and it’s easy to trip over an off-by-one error or an accidental mismatch.

Python’s enumerate() function solves this neatly by giving you the index and the item—together:

for index, item in enumerate(items):
    print(index, item)

# 0 a
# 1 b
# 2 c

Clean. Readable. No need to poke into the list using square brackets. Just ask Python to hand you both things you need.

Controlling the Starting Index #

By default, enumerate() starts counting from zero. But if you want the index to start somewhere else—say, 1—you can pass a second argument:

for index, item in enumerate(items, start=1):
    print(index, item)

# 1 a
# 2 b
# 3 c

This is useful when displaying numbered lists to users, or if your data is 1-based (as spreadsheets and humans tend to be).

When to Use enumerate() #

Use it whenever:

  • You need both the index and the value
  • You want to keep your loop readable and maintainable
  • You're avoiding the temptation to mix range() and len() for iteration

It’s a small change, but it makes your code feel more intentional—and in larger codebases, clarity is more than just nice to have.

Summary #

  • enumerate() simplifies loops where index matters
  • Keeps code cleaner than range(len(...))
  • Optional start argument adds flexibility

It’s one of those features that feels minor—until you go back to writing loops without it.

Loop Control with break and continue #

Python’s for loop goes through every item unless you tell it otherwise. That’s where break and continue come in. These two control statements let you adjust the flow mid-loop, giving you the option to stop early or skip selectively.

They’re simple tools, but when used well, they make your loops more flexible and expressive.

break: Exit Early #

The break statement immediately exits the loop—no further iterations, no questions asked. It’s most commonly used when you’re searching for something and want to stop once you’ve found it.

numbers = [3, 7, 12, 18, 21]

for number in numbers:
    if number > 10:
        print("Found a number greater than 10:", number)
        break

# Found a number greater than 10: 12

After 12 is found, the loop ends—18 and 21 never even get a chance.

Common use cases #

  • Searching for an item
  • Validating data (exit on first failure)
  • Exiting loops based on a condition that doesn’t make sense to check further

Just make sure it’s obvious why the loop is ending early. A well-placed comment doesn’t hurt.

continue: Skip and Move On #

The continue statement on the other hand skips the current iteration and moves to the next one.

for number in range(5):
    if number == 2:
        continue
    print(number)

# 0
# 1
# 3
# 4

Here, 2 is simply skipped. The loop goes on as if it never existed—useful when filtering or ignoring certain values.

Common use cases #

  • Skipping over bad data
  • Ignoring empty strings or None values Bypassing certain branches of logic

Just be cautious—overusing continue can lead to loops that are harder to read. If your logic starts looking like a choose-your-own-adventure novel, it might be time to rethink the structure.

Used Together #

break and continue can also appear in the same loop—just be clear about their purpose:

for number in range(10):
    if number == 0:
        continue  # skip zero to avoid division error
    if number > 5:
        break  # only process numbers 1–5
    print(10 / number)

A bit artificial, but it illustrates the point: skip what doesn’t make sense, and exit when you’ve had enough.

Summary #

Statement What It Does Typical Use Case
break Exits the loop immediately Stop once a condition is met
continue Skips to the next iteration Skip invalid or unwanted cases

Used well, these control tools make your loops precise and intention-driven. Just don’t overdo it—sometimes a cleaner loop structure is better than a clever one.

Looping Over Multiple Iterables with zip() #

Sometimes you’re working with two (or more) related sequences—names and scores, questions and answers, keys and values stored in parallel lists. Iterating over them side by side is a common task, and Python’s zip() function makes that both elegant and reliable.

Think of zip() as a way to "pair up" the elements of multiple iterables, forming tuples of matching elements along the way.

Basic Usage #

Let’s say you’ve got two lists:

names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]

To print each name with the corresponding score, you can write:

for name, score in zip(names, scores):
    print(f"{name}: {score}")

# Alice: 85
# Bob: 92
# Charlie: 78

Python combines the first elements of both lists, then the second, and so on—like a zipper closing up two sides.

What Happens with Uneven Lengths? #

If the iterables are of different lengths, zip() stops at the shortest one:

names = ['Alice', 'Bob']
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

# Alice: 85
# Bob: 92

No errors, no warnings—Python simply stops where it makes sense. If you need to handle the leftover items, consider using itertools.zip_longest() instead.

Real-World Use Cases #

zip() comes in handy when:

  • Combining data from parallel structures (e.g., lists of questions and answers)
  • Iterating through multiple columns of data
  • Rebuilding dictionaries:
keys = ['name', 'age']
values = ['Alice', 30]
person = dict(zip(keys, values))

This is one of the most Pythonic ways to merge keys and values into a dictionary—brief, clear, and readable.

Unzipping: Going the Other Way #

You can also reverse the process. If you’ve zipped a list of pairs, you can unzip it using the * operator:

pairs = [('a', 1), ('b', 2), ('c', 3)]
letters, numbers = zip(*pairs)

Now letters becomes ('a', 'b', 'c') and numbers becomes (1, 2, 3). It's a neat trick that's good to keep in your back pocket.

Summary #

  • zip() lets you iterate over multiple iterables in parallel
  • Stops at the shortest iterable by default
  • Great for pairing data, building dictionaries, and improving code clarity
  • If data length mismatch matters, consider itertools.zip_longest()

In short, if you’ve ever written for i in range(len(...)) just to access two lists side by side, zip() is probably what you actually wanted.

Looping Over Nested Iterables with itertools.chain() #

Sometimes data doesn’t come in one tidy list—it comes in layers. A list of lists, for example, is a common structure when you're dealing with grouped or hierarchical data. And while nested loops can handle this just fine, Python offers a more streamlined option when you want to treat all the inner elements as one continuous sequence.

That’s where itertools.chain() comes in.

The Problem: Nested Iterables #

Let’s say you have this:

groups = [
    [1, 2],
    [3, 4],
    [5, 6]
]

You could loop over it using nested loops:

for group in groups:
    for number in group:
        print(number)

That works. But if you’re not interested in the grouping structure and just want all the numbers in one flat sequence, chain() gives you a cleaner path.

The Solution: chain() from itertools #

itertools.chain() flattens multiple iterables into a single sequence:

from itertools import chain

for number in chain([1, 2], [3, 4], [5, 6]):
    print(number)

Or, using our earlier groups list:

for number in chain.from_iterable(groups):
    print(number)

Output:

1
2
3
4
5
6

No nested loops. Just one pass over the data, as if it were one big list to begin with.

Why Use chain()? #

  • Cleaner syntax when nesting isn’t part of the logic
  • Works with any iterable (lists, tuples, generators, etc.)
  • Efficient—especially with large datasets, since it avoids creating intermediate lists

If you're pulling data from multiple sources and want to loop through all of it without worrying about the container structure, chain() is often the most elegant tool for the job.

For more on how generators can simplify such operations, see Generators in Python.

Bonus: Combining Separate Lists #

chain() isn’t just for flattening. You can also use it to combine multiple lists:

a = [1, 2]
b = [3, 4]
c = [5, 6]

for number in chain(a, b, c):
    print(number)

This gives the same result as [1, 2] + [3, 4] + [5, 6], but without creating a new combined list in memory first. That can make a difference if the iterables are large or streaming.

Summary #

  • itertools.chain() flattens or combines iterables into one seamless sequence
  • Use .from_iterable() when working with a list of lists
  • Avoids nesting and improves readability when structure doesn’t matter
  • Ideal for processing multi-part data as a single stream

While nested loops certainly have their place, chain() is often the better tool when you don’t need to treat inner lists differently—it helps your code express intent more clearly, and that’s never a bad thing.

Sorting and Reversing in Loops #

Python gives you a few simple, flexible tools to loop over data in a specific order—whether that means sorting it, reversing it, or both. Importantly, these operations don’t require you to modify the original data (unless you explicitly want to), which makes your code safer and more predictable.

Let’s look at two common built-in functions: sorted() and reversed().

Looping in Sorted Order: sorted() #

The sorted() function returns a new sorted list from any iterable. It leaves the original data untouched, which makes it a safe choice when you need to sort temporarily—for display, comparison, or output.

numbers = [5, 2, 9, 1]

for num in sorted(numbers):
    print(num)

# 1
# 2
# 5
# 9

Notice: numbers stays exactly as it was. If you want to sort in-place, you’d use .sort()—but for most loop scenarios, sorted() is safer and more flexible.

Sorting in Reverse #

Just add the reverse=True flag:

for num in sorted(numbers, reverse=True):
    print(num)

# 9
# 5
# 2
# 1

Custom Sorting with key= #

You can also sort using a custom function:

words = ['banana', 'apple', 'cherry']

for word in sorted(words, key=len):
    print(word)

# apple
# banana
# cherry

Here, the words are sorted by length. You’re not limited to len()—any function that returns a sortable value can be used.

This is especially useful when working with objects or complex data:

people = [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}]

for person in sorted(people, key=lambda p: p['age']):
    print(person['name'])

# Bob
# Alice

Looping in Reverse: reversed() #

The reversed() function gives you the same items, but in reverse order:

letters = ['a', 'b', 'c']

for letter in reversed(letters):
    print(letter)

# c
# b
# a

Just like sorted(), this doesn’t modify the original data. It simply gives you a view of it moving backward.

Note: reversed() only works with sequences (like lists or tuples)—not arbitrary iterables. If you need to reverse something like a generator, convert it to a list first.

Summary #

Function Purpose Mutates Original?
sorted() Returns a sorted version No
.sort() Sorts in place (lists only) Yes
reversed() Returns a reverse view No

Whether you're sorting numbers, reversing strings, or ordering complex data by a custom rule, these functions let you control the flow of your loops without extra steps or temporary variables.

Comprehensions vs for Loops #

There’s a moment in every Python developer’s journey when they discover comprehensions—and wonder how they ever lived without them.

Comprehensions are a compact, expressive way to generate new sequences from existing ones. They often replace short for loops with a single, readable line. But while they can improve clarity and performance, they’re not always the right tool.

For a detailed dive into comprehensions, see Python Comprehensions.

List Comprehensions #

A list comprehension is a concise way to create a new list by transforming or filtering items from an iterable.

Here’s the classic example:

squares = [x**2 for x in range(5)]

Equivalent for loop:

squares = []
for x in range(5):
    squares.append(x**2)

Both do the same thing, but the comprehension gets to the point faster. If the transformation is simple and side-effect-free, comprehensions tend to be the more Pythonic choice.

With a Condition: Filtering #

You can also add an if clause to filter items:

evens = [x for x in range(10) if x % 2 == 0]

This gives you just the even numbers. Clean, readable, and no need to manually check inside the loop.

Set and Dictionary Comprehensions #

The same syntax works with other collection types:

Set comprehension:

unique_lengths = {len(word) for word in ['hi', 'hello', 'world']}

Dictionary comprehension:

name_lengths = {name: len(name) for name in ['Alice', 'Bob', 'Charlie']}

These are just as expressive and tend to communicate intent well, especially for small, self-contained operations.

When to Stick With for Loops #

Not every loop should be compressed into one line. Sometimes the logic is too involved, or you’re doing more than just building a list.

Avoid comprehensions when:

  • The loop includes multiple steps or side effects
  • You’re modifying state outside the list being built
  • Readability suffers from cramming too much into one expression

For example:

# Not ideal
[print(x) for x in range(5)]

This works, but it’s not what comprehensions are for. If your goal isn’t to produce a new list, a for loop is the clearer, more honest approach.

Summary #

Use for loops when... Use comprehensions when...
Logic spans multiple lines or steps You’re transforming or filtering items cleanly
You’re performing actions (e.g., printing) You’re building a new collection
Readability matters more than brevity You want concise, expressive transformation logic

Comprehensions are great—powerful, concise, and elegant. But like any power tool, they’re best used with intention. When in doubt, write it long-form first. If it reads cleanly as a comprehension, refactor. If not, leave it be.

Safe and Unsafe Operations Inside Loops #

Loops are powerful, but with great power comes the occasional head-scratcher. Some operations are perfectly safe inside a loop—others can quietly cause bugs or performance problems if you’re not careful.

Let’s look at what’s safe to do inside a for loop in Python… and what might deserve a second thought.

Safe Operations #

Most everyday tasks inside a loop are perfectly fine. You’re free to:

  • Read data from the looped item
  • Perform calculations
  • Append to a new list or dictionary
  • Print or log values
  • Call functions that don’t mutate the loop source

Examples:

numbers = [1, 2, 3]

for n in numbers:
    print(n * 2)  # Safe: simple operation

Or building a transformed list:

results = []
for n in numbers:
    results.append(n * 2)  # Safe: appending elsewhere

No surprises here. You’re working with the data, not against it.

Risky Business: Modifying the Iterable While Looping #

Here’s where things get trickier.

Modifying the iterable you’re currently looping over—especially a list or dictionary—can lead to confusing behavior or silent bugs. This is especially common when removing or inserting items.

Problem Example:

numbers = [1, 2, 3, 4]

for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)  # Risky

You might expect [1, 3] at the end. But you’ll likely end up with [1, 3, 4]—because removing an item shifts the list, and the loop moves on without realizing it skipped something.

Safer Alternative: Loop Over a Copy

for n in numbers[:]:  # Shallow copy
    if n % 2 == 0:
        numbers.remove(n)  # Safe now

Or, better yet, use list comprehension to build a new list:

numbers = [n for n in numbers if n % 2 != 0]

Mutating Dictionaries: Same Caution Applies #

Modifying a dictionary while looping over it (adding or deleting keys) raises a RuntimeError in most cases. If you need to change a dictionary mid-loop, loop over a copy of the keys:

for key in list(my_dict.keys()):
    if should_remove(key):
        del my_dict[key]

Appending to the Same List: Depends #

Appending to the list you’re looping over? Technically legal, but often a sign that the code may spiral.

items = [1, 2, 3]

for item in items:
    items.append(item * 2)  # This never ends well

This will likely create an infinite loop or at least unexpected results. It’s best to separate reading from writing when working with collections in a loop.

Summary #

Action Safe? Notes
Reading items Standard use case
Appending to a separate collection Common in transformations
Modifying the iterable in-place Can cause skipped items or runtime errors
Looping over a copy Use [:] for lists or list(dict.keys()) for dicts
Appending to the iterable being looped ⚠️ May lead to infinite or unintended behavior

In short: if you're making structural changes to the iterable mid-loop, it's worth pausing to ask whether there's a better (and safer) approach. Python won't always stop you—but future you might wish it had.

Best Practices and Performance Tips for for Loops #

Python’s for loop is built for clarity and ease of use, but that doesn’t mean all loops are created equal. A well-structured loop can be efficient and readable. A poorly designed one… can quietly eat your CPU or leave future readers scratching their heads.

Here are a few principles and tips to help you write better loops—both in terms of performance and maintainability.

1. Prefer Direct Iteration Over Indexing #

If you don’t need the index, don’t use it. Loop directly over the iterable:

# Better
for item in items:
    ...

# Avoid unless you need the index
for i in range(len(items)):
    item = items[i]

Fewer moving parts usually means fewer bugs.

2. Use enumerate() When You Do Need the Index #

Avoid juggling indices manually when Python can give them to you cleanly:

for index, item in enumerate(items):
    ...

It’s more readable and reduces the chance of mismatched access.

3. Avoid Repeated Work Inside the Loop #

Move expensive operations out of the loop body when possible.

# Inefficient
for item in data:
    if lookup_value in expensive_function():  # runs every time
        ...

# Better
result = expensive_function()
for item in data:
    if lookup_value in result:
        ...

Each call to a function, database, or file system inside a loop is a potential bottleneck.

4. Avoid Modifying the Loop Iterable #

As discussed earlier, changing the iterable while iterating over it (especially lists and dicts) can lead to unintended behavior. If modification is necessary, loop over a copy or build a new structure instead.

5. Leverage Comprehensions for Simpler Loops #

For quick filtering or transformation tasks, comprehensions offer both performance and readability benefits—when used in moderation.

# Good
squares = [x**2 for x in range(10)]

# Maybe not
squares = [x**2 for x in range(10) if x % 2 == 0 and x > 4 and some_other_check(x)]

If a comprehension feels like a riddle, it’s time to break it back into a loop.

6. Use Built-in Functions Where Possible #

Often, Python provides a built-in function that does exactly what your loop is doing—only faster and more idiomatically.

Examples:

  • Use sum() instead of a loop that adds numbers
  • Use any() or all() instead of custom flag logic
  • Use map() or filter() for straightforward transformations
# Instead of:
total = 0
for x in numbers:
    total += x

# Use:
total = sum(numbers)

7. Use Generators for Large Data #

When working with large datasets, prefer generators over building large lists:

# Generator expression
squares = (x**2 for x in range(10**6))

# Loop through it efficiently
for sq in squares:
    process(sq)

This avoids loading the entire result into memory at once.

Summary #

Tip Why It Matters
Loop directly over data Cleaner and easier to read
Use enumerate() wisely Avoid manual index handling
Don’t repeat expensive operations Keeps loops fast
Avoid mutating the iterable Prevents subtle bugs
Use comprehensions when appropriate Keeps simple transformations compact
Favor built-ins More idiomatic and often faster
Use generators for large data Saves memory and improves scalability

A well-written for loop should be boring—in the best way. It should do one thing clearly, without surprises. With these practices in hand, you’ll not only write better loops, but you’ll make your code easier to debug, extend, and share.

Conclusion #

Python’s for loop is one of those features that feels effortless—until you realize how much thought and power it actually hides. From looping over simple lists to combining complex data structures with zip(), flattening nested lists with chain(), or customizing behavior with enumerate(), Python makes iteration expressive without being verbose.

Along the way, we’ve covered:

  • The basics of looping over different iterable types
  • Controlling flow with break and continue
  • Combining and flattening iterables
  • Sorting, reversing, and indexing
  • Looping safely without falling into common pitfalls
  • Choosing between loops and comprehensions based on clarity and intent
  • A few best practices to keep your code clean, readable, and fast

What stands out across all of this is that Python encourages intention in your loops. Whether you’re looping over a few items or streaming through millions, the tools are there to help you write logic that’s both precise and expressive.

And perhaps that’s the real lesson: in Python, a for loop isn’t just about repetition—it’s about communicating purpose.