Python enumerate(): Simplify Looping With Counters

Python enumerate(): Simplify Loops That Need Counters

by Philipp Acsany Jun 23, 2025 basics best-practices

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Looping With Python enumerate()

Python’s enumerate() function helps you with loops that require a counter by adding an index to each item in an iterable. This is particularly useful when you need both the index and value while iterating, such as listing items with their positions. You can also customize the starting index with the start argument, offering flexibility in various scenarios.

By the end of this tutorial, you’ll understand that:

  • enumerate() in Python adds a counter to an iterable and returns it as an enumerate object, pairing each item with an index.
  • enumerate() accepts an iterable and an optional start argument to set the initial index value.
  • The advantage of enumerate() is that it provides both index and value pairs without manual counter management.
  • You can use zip() or slicing as alternatives to enumerate() for iterating multiple sequences or selecting specific elements.

Explore how to implement your own version of enumerate() and discover alternative methods like zip() and itertools for more complex iteration patterns.

Using Python’s enumerate()

There are situations when you need both the index and the value of items in an iterable. That’s when Python’s enumerate() function can come in handy for you.

Python’s enumerate() function adds a counter to an iterable and returns it in the form of an enumerate object, which can be used directly in loops. For example, when you want to display the winners of a race, you might write:

Python
>>> runners = ["Lenka", "Martina", "Gugu"]
>>> for winner in enumerate(runners):
...     print(winner)
...
(0, 'Lenka')
(1, 'Martina')
(2, 'Gugu')

Since enumerate() is a built-in function, you can use it right away without importing it. When you use enumerate() in a for loop, you get pairs of count and value from the iterable.

In the example above, you’re storing the position and the runner’s name in a variable named winner, which is a tuple. To display the position and the name right away, you can unpack the tuple when you define the for loop:

Python
>>> runners = ["Lenka", "Martina", "Gugu"]
>>> for position, name in enumerate(runners):
...     print(position, name)
...
0 Lenka
1 Martina
2 Gugu

Just like the winner variable earlier, you can name the unpacked loop variables whatever you want. You use position and name in this example, but they could just as easily be named i and value, or any other valid Python names.

When you use enumerate() in a for loop, you typically tell Python to use two variables: one for the count and one for the value itself. You’re able to do this by applying a Python concept called tuple unpacking.

Unpacking is the idea that a tuple—or another type of a Python sequence—can be split into several variables depending on the length of that sequence. For instance, you can unpack a tuple of two elements into two variables:

Python
>>> cart_item = ("Wireless Mouse", 2)
>>> product_name, quantity_ordered = cart_item
>>> product_name
'Wireless Mouse'
>>> quantity_ordered
2

First, you create a tuple with two elements, "Wireless Mouse" and 2. Then, you unpack that tuple into product_name and quantity_ordered, which are each assigned one of the values from the tuple.

When you call enumerate() and pass a sequence of values, Python returns an iterator. When you ask the iterator for its next value, it yields a tuple with two elements. The first element of the tuple is the count, and the second element is the value from the sequence that you passed:

Python
>>> values = ["first", "second"]

>>> enumerate_instance = enumerate(values)
>>> print(enumerate_instance)
<enumerate at 0x7fe75d728180>

>>> next(enumerate_instance)
(0, 'first')

>>> next(enumerate_instance)
(1, 'second')

>>> next(enumerate_instance)
Traceback (most recent call last):
  File "<python-input-5>", line 1, in <module>
    next(enumerate_instance)
    ~~~~^^^^^^^^^^^^^^^
StopIteration

In this example, you create a Python list called values with two elements, "first" and "second". Then, you pass values to enumerate() and assign the return value to enumerate_instance. When you print enumerate_instance, you’ll see that it’s an enumerate object with a particular memory address.

Then, you use Python’s built-in next() to get the next value from enumerate_instance. The first value that enumerate_instance returns is a tuple with the count 0 and the first element from values, which is "first".

Calling next() again on enumerate_instance yields another tuple—this time, with the count 1 and the second element from values, "second". Finally, calling next() one more time raises a StopIteration exception since there are no more values to be returned from enumerate_instance.

When an iterable is used in a for loop, Python automatically calls next() at the start of every iteration until StopIteration is raised. Python assigns the value it retrieves from the iterable to the loop variable.

By default, the count of enumerate() starts at 0. This isn’t ideal when you want to display the winners of a race. Luckily, Python’s enumerate() has one additional argument that you can use to control the starting value of the count.

When you call enumerate(), you can use the start argument to change the starting count:

Python
>>> runners = ["Lenka", "Martina", "Gugu"]
>>> for position, name in enumerate(runners, start=1):
...     print(position, name)
...
1 Lenka
2 Martina
3 Gugu

In this example, you pass start=1, which initializes position with the value 1 on the first loop iteration.

You should use enumerate() anytime you need to use the count and an item in a loop. Keep in mind that enumerate() increments the count by one on every iteration. However, this only slightly limits your flexibility. Since the count is a standard Python integer, you can use it in many ways.

Practicing With Python enumerate()

Now that you’ve explored the basics of enumerate(), it’s time to practice using enumerate() with some real-world examples.

Performing an Action Based on the Loop Step

Using conditional statements to process items can be a very powerful technique. Sometimes, you might need to perform an action on only the very first iteration of a loop, as shown in this example:

Python
>>> tasks_by_priority = ["Pay Rent", "Clean Dishes", "Buy Milk"]
>>> for index, task in enumerate(tasks_by_priority):
...     if index == 0:
...         print(f"* {task.upper()}!")
...     else:
...         print(f"* {task}")
...
* PAY RENT!
* Clean Dishes
* Buy Milk

In this example, you use a list of tasks that are ordered by priority. The first task is your most important task, so you want to emphasize this task. When you loop through tasks_by_priority, you print the task "Pay Rent" in uppercase letters and with an exclamation mark (!) because it’s the first item.

Calling enumerate() allows you to control the flow of a loop based on the index of each item in a list, even if the values themselves aren’t important to your logic.

Getting Every Second Item of an Iterable

You can also combine mathematical operations with conditions for the index. For example, you might need to grab every second item from a Python string. You can do this by using enumerate():

Python
>>> secret_message = "3LAigf7eq 5fhiOnpdDs2 Ra6 nwUalyo.9"
>>> message = ""
>>> for index, char in enumerate(secret_message):
...     if index % 2:
...         message += char
...
>>> print(message)

Here, you’ve received a mysterious string of characters in a secret_message variable. To decipher the message, you loop through secret_message with enumerate() and access every character at an odd index.

To get only the characters at odd indexes, you check whether dividing the index by 2 leaves a nonzero remainder—something that’s considered truthy in a Boolean context like this. That way, you add every second character from secret_message to message. Copy the code and run it to reveal what the message says!

Referencing Line Numbers in a File

Suppose you want to check lines of a file for certain formatting issues, such as trailing whitespace or tab characters, and report these with a line number. Once you stored the lines of the file in a list, you can traverse through them with enumerate():

Python
>>> lines = [
...     "This is a\tline",
...     "This line is fine",
...     "Another line with whitespace "
... ]

>>> for lineno, line in enumerate(lines, start=1):
...     if "\t" in line:
...         print(f"Line {lineno}: Contains a tab character.")
...     if line.rstrip() != line:
...         print(f"Line {lineno}: Contains trailing whitespace.")
...
Line 1: Contains a tab character.
Line 3: Contains trailing whitespace.

You use enumerate() to loop over lines and get the line number and each line of text. Then, you use two if statements to check for out-of-place characters and print a message. By setting start to 1, lineno isn’t a zero-based counter. That way, you get the correct line number of the lines in the file.

Instead of using consecutive if statements, you could also leverage structural pattern matching. If you’re curious and would like to learn more, then check out the tutorial on structural pattern matching in Python.

Understanding Python enumerate()

In the last few sections, you saw examples of when and how to use enumerate() to your advantage. Now that you’ve got a handle on the practical aspects of enumerate(), you can learn more about how the function works internally.

To get a better sense of how enumerate() works, you can implement your own version with Python. Your version of enumerate() has two requirements. It should:

  1. Accept an iterable and a starting count value as arguments
  2. Send back a tuple with the current count value and the associated item from the iterable

One way to write a function that meets these specifications is given in the Python documentation:

Python
>>> def my_enumerate(iterable, start=0):
...     n = start
...     for elem in iterable:
...         yield n, elem
...         n += 1
...

my_enumerate() takes two arguments, iterable and start, where the default value of start is 0. Inside the function definition, you initialize n to be the value of start and run a for loop over the iterable.

For each elem in iterable, you yield control back to the calling location and send back the current values of n and elem. Finally, you increment n to get ready for the next iteration. You can see my_enumerate() in action here:

Python
>>> seasons = ["Spring", "Summer", "Fall", "Winter"]

>>> my_enumerate(seasons)
<generator object my_enumerate at 0x7f48d7a9ca50>

>>> list(my_enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

>>> list(my_enumerate(seasons, start=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

First, you create a list of the four seasons to work with. Next, you show that calling my_enumerate() with seasons as the iterable creates a generator object. This is because you use the yield keyword to send values back to the caller.

Finally, you create two lists from my_enumerate(): one where the start value is left as the default, 0, and another in which start is changed to 1. In both cases, you end up with a list of tuples where the first element of each tuple is the count and the second element is the value from seasons.

Although you can implement an equivalent function for enumerate() in only a few lines of Python code, the actual code for enumerate() is written in C. This means that it’s super fast and efficient.

Exploring Alternatives to enumerate()

By now, you’ve familiarized yourself with enumerate(), and chances are that you have some use cases in mind. While enumerate() is a powerful tool, there are several scenarios where other approaches are more appropriate. In this section, you’ll have a look at some examples where you could use enumerate() to do the job, but it may not be the best choice.

Using Slicing Instead of enumerate()

Imagine you’ve been assigned the task of printing the first four letters of your favorite video game’s name. You’ve already learned that you can use if conditions inside a for loop that leverages enumerate(). Equipped with this knowledge, you could approach this task like this:

Python
>>> game_name = "Stardew Valley"
>>> first_letters = ""
>>> for i, letter in enumerate(game_name):
...     if i < 4:
...         first_letters += letter
...     else:
...         break
...
>>> print(first_letters)
Star

In this example, you create an empty string named first_letters, which you fill up with the first four letters by looping through game_name using enumerate(). To just get the first four letters, you add the if condition and exit the loop early with the break keyword afterward.

That’s quite some code for a basic task like getting the first four letters of a string! Sure enough, there’s a better way, and it only takes one line of code:

Python
>>> print(game_name[:4])
Star

This operation is known as slicing, and it’s the common way to get a portion of a sequence like a string.

When you only need specific ranges or patterns from a string, direct slicing is more efficient and concise than using enumerate() with conditional logic.

Using zip() for Multi-Sequence Iteration

Suppose you have a list of pets and another list of their owners. Your task is to print tuples where the first item is the pet’s name and the second item is the owner’s name. You could accomplish this with enumerate(), but it’s not the best approach:

Python
>>> pets = ["Leo", "Aubrey", "Frieda"]
>>> owners = ["Bartosz", "Sarah Jane", "Philipp"]

>>> for i, pet in enumerate(pets):
...     print(f"{pet} & {owners[i]}")
...
Leo & Bartosz
Aubrey & Sarah Jane
Frieda & Philipp

In the example above, you’re using enumerate() to get the index i and pet in your loop. Then, you use the index to access the item in owners. The code works but it’s a bit complicated because you’re creating i just to work with the index.

Instead, it would be cleaner to aggregate both lists with zip() and loop through the iterator that the function returns:

Python
>>> pets = ["Leo", "Aubrey", "Frieda"]
>>> owners = ["Bartosz", "Sarah Jane", "Philipp"]

>>> for pet, owner in zip(pets, owners):
...     print(f"{pet} & {owner}")
...
Leo & Bartosz
Aubrey & Sarah Jane
Frieda & Philipp

While the result is the same, the intention of the for loop definition is clearer when you use zip().

When you’re trying to iterate over multiple sequences simultaneously, it’s usually a good idea to leverage Python’s built-in function zip(). If you want to learn more about zip(), then you can check out Using the Python zip() Function for Parallel Iteration.

Using itertools for Advanced Iteration Patterns

For more complex iteration patterns, the itertools module often provides better alternatives than enumerate(). For example, suppose you’re planning a road trip and want to tell a friend about your route.

First, start with enumerate() and use the patterns you already know:

Python
>>> cities = ["Graz", "Belgrade", "Warsaw", "Berlin"]
>>> for index, city in enumerate(cities[:-1]):
...     print(f"{city} -> {cities[index + 1]}")
...
Graz -> Belgrade
Belgrade -> Warsaw
Warsaw -> Berlin

Notice how you must slice the cities list that you pass into enumerate() to ensure that you don’t loop until the end. Then, in the body of the for loop, you’re accessing the cities as consecutive pairs.

For an advanced iteration pattern, like accessing consecutive pairs, it’s worth having a look what Python’s itertools has to offer. In this case, itertools.pairwise() does the trick:

Python
>>> from itertools import pairwise
>>> cities = ["Graz", "Belgrade", "Warsaw", "Berlin"]
>>> for city, next_city in pairwise(cities):
...     print(f"{city} -> {next_city}")
...
Graz -> Belgrade
Belgrade -> Warsaw
Warsaw -> Berlin

The itertools module is part of Python’s standard library and not built-in like enumerate(). That’s why you need to import itertools.pairwise() before you can use it.

If you want to learn more about itertools, then you can find a fantastic conversation in the Real Python Podcast Episode 252 with Rodrigo Girão Serrão.

Conclusion

Python’s enumerate() lets you write Pythonic for loops when you need a count and the value from an iterable. The significant advantage of enumerate() is that it returns a tuple with the counter and value, so you don’t have to increment the counter yourself. It also gives you the option to change the starting value for the counter.

In this tutorial, you learned how to:

  • Use Python’s enumerate() in your for loops
  • Apply enumerate() in a few real-world examples
  • Get values from enumerate() using tuple unpacking
  • Implement your own equivalent function to enumerate()

You’ve also explored alternative methods like zip() and itertools for more complex iteration patterns to help you decide when not to use enumerate().

Frequently Asked Questions

Now that you have some experience with Python’s enumerate() function, you can use the questions and answers below to check your understanding and recap what you’ve learned.

These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.

enumerate() adds a counter to an iterable and returns it as an enumerate object, which you can use in loops to access both the index and the value of items.

enumerate() accepts an iterable and an optional start argument that sets the starting value of the counter. The default value of start is 0.

enumerate() provides both the index and the value of items in an iterable, removing the need to manually increment a counter.

For iterating over multiple sequences, use zip(), and for more advanced iteration patterns, consider using the itertools module.

Take the Quiz: Test your knowledge with our interactive “Python's enumerate()” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Python's enumerate()

Once you learn about for loops in Python, you know that using an index to access items in a sequence isn't very Pythonic. So what do you do when you need that index value? In this tutorial, you'll learn all about Python's built-in enumerate(), where it's used, and how you can emulate its behavior.

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Looping With Python enumerate()

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Philipp Acsany

Philipp is a core member of the Real Python team. He creates tutorials, records video courses, and hosts Office Hours sessions to support your journey to becoming a skilled and fulfilled Python developer.

» More about Philipp

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!