Have you ever been looping through a list in Python and everything’s going fine, right up until the point where you realize you need the position of each item too?
Maybe you want to number them, or perhaps you need the index for something else, but that’s when things get a little complicated - especially if you’re looping with range() and manually indexing into the list.
The good news though is that Python has a built-in tool for this exact situation, and it’s called enumerate(). This function is super simple to use, and once you understand how it works, you’ll see all sorts of opportunities to improve your code with it.
In this guide I’ll break down how it works, when to use it, when to skip it, and how to get past the common mistakes.
Let’s dive in…
Sidenote: If you find any of this confusing, or simply want a deep dive into Python, check out Andrei's Python Coding course taken by 200,000+ people:
It’ll take you from an absolute beginner to understanding everything you need to be hired ASAP.
Alternatively, if you're already pretty good at Python and want to build some interesting and useful projects, why not check out my course on Python Automation:
It'll show you how to automate all of the boring or repetitive tasks in your life - and makes for some pretty standout portfolio projects as well!
With all that out of the way, let's get into this 5-minute tutorial!
enumerate() is a built-in Python function that lets you loop through any
That’s all it does.
However, it turns out that being able to get both the value and the index in a loop is something you’ll need to do more often than you’d expect.
For example
Imagine you’re writing a small terminal-based to-do list app.
Nothing too fancy, just something that prints out a list of tasks and numbers them so users can choose one to mark as complete:
0: Walk the dog
1: Take out the trash
2: Buy milk
So now you’ve got a list of tasks, and you want to print each one with its position. However, if you haven’t seen enumerate() before, the obvious first solution might be something like this:
tasks = ['Walk the dog', 'Take out the trash', 'Buy milk']
for i in range(len(tasks)):
print(f"{i}: {tasks[i]}")
Here’s the cleaner solution using enumerate():
for i, task in enumerate(tasks):
print(f"{i}: {task}")
So what’s happening here?
enumerate(tasks) gives you a pair which is the index and the item
Python lets you unpack both in one line: i gets the index, task gets the value
No more range(len(...)) gymnastics or reaching into the list by hand
And best of all, when you glance at that loop, it immediately tells you what it’s doing, because it’s clean, clear, and hard to misinterpret.
Simple right?
So let’s look at a few more examples of enumerate() in action, applied to scenarios where it really shines.
So let's take this a step further.
In the example I just gave, we printed a task list using enumerate() and saw why numbering the items in an iterable can make them easier to work with. However,
First, here’s the list:
tasks = ['Walk the dog', 'Take out the trash', 'Buy milk']
for i, task in enumerate(tasks):
print(f"{i}: {task}")
That gives us:
0: Walk the dog
1: Take out the trash
2: Buy milk
Now let’s ask the user which task they completed:
choice = int(input("Which task did you complete? "))
print(f"You marked task {choice} as done: {tasks[choice]}")
This works cleanly because the number you printed next to each task is the same as its actual position in the list. So when the user types 1, it pulls out the correct task with tasks[1].
Handy right?
And sure, if you’d written this loop with range(len(...)), you’d get the same result, but it would take more effort and more code. Using enumerate() just keeps everything lined up without you needing to think about it.
These sorts of things come up all the time, so being able to simplify them can improve your quality of life in a major way.
By default, enumerate() starts counting from 0, which makes sense if you’ve been coding for a while.
The problem of course is that the average user on the street is going to assume that lists start at 1. So making your lists start at 0 instead can be confusing for them.
The good news is we can fix this by customizing which number our list starts at, in this case to 1.
So instead of:
0: Walk the dog
1: Take out the trash
2: Buy milk
We can have:
1: Walk the dog
2: Take out the trash
3: Buy milk
And the solution is actually super simple. You just need to tell enumerate() where to start:
for i, task in enumerate(tasks, start=1):
print(f"{i}: {task}")
Easy right? The loop works exactly the same as before, except that now the numbers begin at 1. That makes for a much friendlier user experience, and without you needing to write any extra logic.
You can also use other numbers if you have a specific reason.
For example
start=100 if you're assigning ticket numbers
start=42 if you're continuing a list from earlier
start=2 if the first item is reserved
Just remember though, that if you’re going to use that modified/offset index number to look up its associated item later, you may need to adjust it back:
choice = int(input("Which task did you complete? "))
index = choice - 1 # adjust back
print(f"You marked task {choice} as done: {tasks[index]}")
This way you keep the output of your program user-friendly, while still working safely with Python’s 0-based indexing behind the scenes.
Sometimes you’re processing a list of items such as files, tasks, or emails, and you want to display an indication of your program’s progress while it runs.
For example, maybe the program takes a few seconds to process each item, and you just want to print a line per item to keep track of what’s happening.
This is another situation where enumerate() can be incredibly helpful.
For example
Let’s say you’re working through a list of files:
files = ['invoice1.pdf', 'invoice2.pdf', 'invoice3.pdf']
And you want to print something like:
Processing item 1 of 3: invoice1.pdf
Processing item 2 of 3: invoice2.pdf
Processing item 3 of 3: invoice3.pdf
for i, filename in enumerate(files, start=1):
print(f"Processing item {i} of {len(files)}: {filename}")
Because enumerate() gives you both the number and the file name, you don’t need to manage a counter or worry about off-by-one errors. It just works.
Now to be sure, you could technically write the same thing with a manual counter:
i = 1
for filename in files:
print(f"Processing item {i} of {len(files)}: {filename}")
i += 1
However, this solution requires you to manage that i yourself, which means adding unnecessary moving parts to the code. But enumerate() allows you to keep your code cleaner in this situation, taking away one more thing that could go wrong.
This pattern is great for:
Long-running scripts where feedback helps
Debugging which item caused an error
Giving the user a sense of progress
It’s a small thing that can nonetheless make your scripts feel more thoughtful, especially when they involve looping through a long list.
User experience matters, and knowing that the code is actually running is always helpful!
Another place enumerate() really helps is when you're comparing two lists. Perhaps you’re checking someone’s answers against the correct ones, or comparing before-and-after versions of something.
For example
Let’s say you're building a quick quiz checker and you’ve got two lists: one containing the submitted answers, and one containing the correct ones.
submitted = ['A', 'B', 'C', 'D']
correct = ['A', 'C', 'C', 'D']
Now you want to go through each answer, compare it to the correct one, and print which questions were wrong. Here’s how you might do it:
for i, answer in enumerate(submitted):
if answer != correct[i]:
print(f"Question {i + 1}: Incorrect (you answered {answer}, correct was {correct[i]})")
This works because enumerate() gives you the index (which matches the question number)
Without enumerate(), you’d be stuck using range(len(...)):
for i in range(len(submitted)):
if submitted[i] != correct[i]:
...
It works, but it’s definitely not as readable; you’re doing more indexing, and mixing logic with structure as well. With enumerate(), the loop does exactly what it says, and gives you the index and the item, one at a time.
This same pattern comes up a lot:
Comparing two lists of data
Checking changes between versions
Validating input vs. expected values
Anytime you need to track what changed and where, enumerate() makes the loop clearer and your code easier to follow.
Sometimes you don’t just want to read from a list, but also to actually update it. Perhaps you're formatting the text, applying some sort of transformation, or even changing certain items based on a logical rule.
The thing is though, if you write a loop like the one below you won’t actually change the list.
for item in my_list:
item = item.upper()
That’s because name is just a temporary variable. It holds a
So if you want to change the list itself and not just its copy, you need to assign a new value directly into the original list. And to do that, you’ll need the value’s index.
This works like so:
names = ['alice', 'bob', 'chris']
for i, name in enumerate(names):
names[i] = name.upper()
After this loop, names becomes:
['ALICE', 'BOB', 'CHRIS']
This is a really common use of enumerate() because it lets you write a clean loop that’s easy to read, yet still gives you access to the index when you need to make changes.
Sure, you could write it like this:
for i in range(len(names)):
names[i] = names[i].upper()
But now you're reaching into the list twice, to say nothing of how much more awkward the loop is to follow. So enumerate() just makes things work and look much smoother.
So whenever you're looping through a list and want to update the original values, this is your go-to pattern.
Even though the mechanics of enumerate() are fairly easy to grasp, there are a few common mistakes that trip people up early on when using it. Most of these come from not fully understanding what Python is actually doing under the hood, and they can lead to confusing errors, or - even worse - silent bugs.
So let’s break down these mistakes and how to fix them.
This is the most common mistake people make with enumerate(), especially if they’ve only seen it used once or twice. You expect it to work like a normal for loop, but instead of giving you just one item at a time, it gives you two: the index and the value.
If you forget to unpack those two parts, your loop behaves in weird ways which can result in confusing output, or even an error.
For example
You might expect this to print just the fruit names:
for item in enumerate(['apple', 'banana']):
print(item)
The thing is, it actually prints:
(0, 'apple')
(1, 'banana')
That’s because enumerate() always returns a pair, (otherwise known as a ‘tuple'), containing the index and the item.
So if you only give Python one variable, it puts the whole pair into that one variable, which becomes a problem if you try to use that variable as though it’s just the item.
For example
for item in enumerate(['apple', 'banana']):
print(item.upper()) # Error!
This crashes, because item is actually a tuple like (0, 'apple'), and the .upper() method doesn’t work on tuples.
The fix? Just tell Python how to unpack both values:
for i, fruit in enumerate(['apple', 'banana']):
print(f"{i}: {fruit.upper()}")
Easy right? Now you’re getting exactly what you expect: i is the index, fruit is the string, and you can use both clearly inside the loop.
Once you understand that enumerate() gives you two things, it becomes much easier to work with.
This one usually pops up when you're moving fast or not quite sure what enumerate() expects, will result in Python throwing up an error like:
So what’s actually going wrong here?
Well, as we know from the last mistake, enumerate() is designed to loop over something that has multiple items, such as a list, a string, or a tuple. In Python terms, that’s called an
But if you accidentally pass in something that isn’t iterable, like an integer, Python doesn’t know what to do.
For example
for i, item in enumerate(42):
print(i, item)
That would crash immediately because you can’t enumerate over a single number. There’s nothing to actually loop through.
Same thing happens if you try:
for i, item in enumerate(None):
...
Again, Python doesn’t know how to loop through None, so it gives you a TypeError.
This mistake usually comes from one of two places:
You meant to pass in a list but accidentally passed a single value.
You’re calling a function that sometimes returns None, and didn’t check before attempting to iterate over that return value.
So how do we prevent these sorts of issues?
Well, the solution depends on what you ’re trying to do, so here’s a few options:
If you meant to loop through a single item, wrap it in a list:
value = 42
for i, item in enumerate([value]):
print(i, item)
Or, if the thing you’re looping through might be None, add a check:
data = get_data() # Might return None
if data:
for i, item in enumerate(data):
...
for i, item in enumerate(data or []):
...
That way, if data is None, nothing happens. There’s no meaningful output but also no crashes or other problems either.
This one is sneaky because it looks like your code runs fine, but then nothing actually happens.
For example
Let’s say you want to capitalize every task in a list, and so you write something like:
tasks = ['walk the dog', 'take out the trash', 'buy milk']
for task in tasks:
task = task.title()
The issue is that when you run this code, the original list doesn’t change at all - it’s still lowercase with no capitalization.
So what gives?
Well, when you write for task in tasks, Python is giving you a
This mistake trips up a lot of people because the code looks like it should work, and even runs error-free. But unless you leverage the indexes of the items you’re iterating over, the original list stays untouched.
Specifically, to actually update the list you'll need to use those indexes to assign the new values back to the correct locations within the list .
Here’s the fix:
for i, task in enumerate(tasks):
tasks[i] = task.title()
Now you're telling Python: “Here’s the position of this task. Go back and update it in the list.”
And after this runs, the list really IS updated:
['Walk The Dog', 'Take Out The Trash', 'Buy Milk']
Sure, you could also do this with range(len(tasks)), but as usual enumerate() reads better and keeps everything in one place.
This pattern is useful any time you need to:
Clean up or transform a list
Modify specific items based on rules
Apply formatting or updates in place
Just remember: if you want to modify the list you’re iterating over, you need the indexes of the items in that list.
And luckily for us, enumerate() makes accessing those indexes and the values associated with them super simple.
So now that you’ve seen what enumerate() does, how it works, and where it’s useful, it’s time to try it out yourself. As with so many things, the best way to learn it is to start using it!
Next time you're writing a loop and you catch yourself reaching for range(len(...)), stop and ask: “Would enumerate() make this easier to read or write?”
It probably will.
You can also use it to number items, keep track of position, or update values in a list — all without adding unnecessary complexity to your code. Once it clicks, you’ll find yourself using it all the time.
Don’t forget, if you want to learn more and dive deep into Python, then be sure to check out Andrei's Complete Python Developer course
It’ll take you from an absolute beginner and teach you everything you need to get hired ASAP and ace the tech interview.
This is the only Python course you need if you want to go from complete Python beginner to getting hired as a Python Developer this year!
Alternatively, if you're already pretty good at Python and want to build some interesting and useful projects, why not check out my course on Python Automation:
It'll show you how to automate all of the boring or repetitive tasks in your life, and makes for some pretty stand out portfolio projects!
Plus, as part of your membership, you'll get access to both of these courses and others, and be able to join me and 1,000s of other people (some who are alumni mentors and others who are taking the same courses that you will be) in the ZTM Discord.
Ask questions, help others, or just network with other Python Developers, students, and tech professionals.
If you enjoyed this post, check out my other Python tutorials: