DEV Community

Cover image for Python enumerate(): How to Start at 1 (and 12 Tested Examples)
Mahir Faysal
Mahir Faysal

Posted on Originally published at mfaysal.com

Python enumerate(): How to Start at 1 (and 12 Tested Examples)

To loop over a list in Python and get a counter that starts at 1, write for n, item in enumerate(items, start=1):. enumerate() returns pairs of (count, item), and the optional start argument sets the first number; it defaults to 0. You can also pass it positionally, as enumerate(items, 1). Changing start only changes the number you see, not which item comes first, so if you use that number to index the list, subtract one.

I ran every example in this post on Python 3.13.5 on 11 October 2026 and pasted the real output under each one. If you’re working through Python basics, this pairs well with my Python list comprehension examples, because enumerate() and comprehensions turn up together all the time.

What enumerate() does

The Python documentation for enumerate() gives the signature as enumerate(iterable, start=0). It returns an enumerate object, an iterator that yields tuples containing a count (starting from start) and the values from the iterable.

Here’s the most basic use:

fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits):
    print(i, fruit)
Enter fullscreen mode Exit fullscreen mode
0 apple
1 banana
2 cherry
Enter fullscreen mode Exit fullscreen mode

The i, fruit part is tuple unpacking. Each loop gets a tuple like (0, 'apple'), and Python splits it into two variables. You can name them anything; i and index are common.

How to make enumerate start at 1

Pass start=1. This is perfect for numbered lists that humans will read, where “item 0” would look odd:

for n, fruit in enumerate(fruits, start=1):
    print(f"{n}. {fruit}")
Enter fullscreen mode Exit fullscreen mode
1. apple
2. banana
3. cherry
Enter fullscreen mode Exit fullscreen mode

The f-string here builds each line; my Python f-string formatting examples cover padding and alignment if you want the numbers to line up in columns.

Two things to keep straight:

  • start changes the counter, not the position. The first item is still fruits[0].
  • If you use the counter to index or change the list, use fruits[n - 1], or simply keep the default start=0 for that loop.

What enumerate() actually returns

enumerate() doesn’t build a list. It returns a lazy iterator that produces one pair at a time, which is why it’s memory-friendly on large data. You can step through it by hand with next() or turn the rest into a list:

e = enumerate(fruits)
print(type(e).__name__, next(e), list(e))
print(list(enumerate("abc", 1)))
Enter fullscreen mode Exit fullscreen mode
enumerate (0, 'apple') [(1, 'banana'), (2, 'cherry')]
[(1, 'a'), (2, 'b'), (3, 'c')]
Enter fullscreen mode Exit fullscreen mode

Two details from that output. Once next(e) has taken the first pair, list(e) only gets what’s left: iterators are used up as you go. And enumerate() works on any iterable, including a string, where each “item” is a character.

How enumerate() works under the hood

There’s no magic inside enumerate(). The Python documentation shows that it’s equivalent to this small generator function:

def enumerate(iterable, start=0):
    n = start
    for elem in iterable:
        yield n, elem
        n += 1
Enter fullscreen mode Exit fullscreen mode

Reading it line by line explains every behaviour in this post. The counter begins at whatever you pass as start. Each step hands back a tuple of the current number and the next element, then adds one. Because it uses yield, nothing is calculated until the loop asks for the next pair, which is why enumerate() works on huge files and endless generators without loading them into memory. And because it simply loops over iterable, it accepts anything a for loop accepts.

You’d never redefine the built-in like this in real code, since it would hide the faster C version, but writing it out once is a good way to understand generators.

enumerate() vs range(len())

Many beginners, me included, first write index loops like this:

for i in range(len(fruits)):
    print(i, fruits[i])
Enter fullscreen mode Exit fullscreen mode
0 apple
1 banana
2 cherry
Enter fullscreen mode Exit fullscreen mode

It works, but enumerate() is usually better:

  • Gives you the item directly: range(len(items)): No, you write items[i]; enumerate(items): Yes.
  • Works with any iterable (files, generators, sets): range(len(items)): No, needs len() and indexing; enumerate(items): Yes.
  • Easy to start counting at 1: range(len(items)): range(1, len(items) + 1) and then items[i - 1]; enumerate(items): start=1.
  • Readability: range(len(items)): More noise; enumerate(items): Says what you mean.

range(len()) still makes sense when you genuinely only need the numbers, or when you’re comparing an item with its neighbour (items[i] and items[i + 1]).

Changing list items in place with enumerate

You can’t change a list by assigning to the loop variable, because that only rebinds the name. You need the index, and enumerate() gives you both:

prices = [10.0, 12.5, 8.0]
for i, p in enumerate(prices):
    prices[i] = round(p * 1.2, 2)
print(prices)
Enter fullscreen mode Exit fullscreen mode
[12.0, 15.0, 9.6]
Enter fullscreen mode Exit fullscreen mode

This adds 20% to each price. Notice I kept the default start=0 here because I’m using i as a real index.

enumerate() with a dictionary

Looping over a dict gives you its keys, so to get keys, values and a counter, call .items() and unpack the inner tuple with brackets:

scores = {"Ayesha": 91, "Ben": 73}
for rank, (name, score) in enumerate(scores.items(), start=1):
    print(rank, name, score)
Enter fullscreen mode Exit fullscreen mode
1 Ayesha 91
2 Ben 73
Enter fullscreen mode Exit fullscreen mode

Dictionaries keep insertion order (guaranteed since Python 3.7), so the ranks follow the order you added the keys. To rank by score instead, sort the items first. With Chloe added on 88:

scores = {"Ayesha": 91, "Ben": 73, "Chloe": 88}
ranked = sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
for rank, (name, score) in enumerate(ranked, start=1):
    print(rank, name, score)
Enter fullscreen mode Exit fullscreen mode
1 Ayesha 91
2 Chloe 88
3 Ben 73
Enter fullscreen mode Exit fullscreen mode

enumerate() with zip(): index plus two lists

zip() walks two lists side by side. Wrap it in enumerate() to number the pairs:

names = ["Ayesha", "Ben", "Chloe"]
marks = [91, 73, 88]
for i, (name, mark) in enumerate(zip(names, marks), start=1):
    print(i, name, mark)
Enter fullscreen mode Exit fullscreen mode
1 Ayesha 91
2 Ben 73
3 Chloe 88
Enter fullscreen mode Exit fullscreen mode

The order matters: enumerate(zip(...)) gives (i, (name, mark)), so you need the brackets around (name, mark) when unpacking.

Looping backwards with reversed and enumerate

reversed() needs a sequence it can walk backwards, and an enumerate object isn’t one. Calling reversed(enumerate(fruits)) directly raised TypeError: 'enumerate' object is not reversible in my test. Convert it to a list first, which keeps the original numbers attached to each item:

for i, fruit in reversed(list(enumerate(fruits, start=1))):
    print(i, fruit)
Enter fullscreen mode Exit fullscreen mode
3 cherry
2 banana
1 apple
Enter fullscreen mode Exit fullscreen mode

If you want the count to go 1, 2, 3 while the items go backwards, do it the other way round:

for i, f in enumerate(reversed(fruits), start=1):
    print(i, f)
Enter fullscreen mode Exit fullscreen mode
1 cherry
2 banana
3 apple
Enter fullscreen mode Exit fullscreen mode

This version works without building a list, because reversed() runs on the original list, and enumerate() happily wraps any iterator.

Line numbers when reading a file

Files are iterables of lines, so enumerate(f, start=1) gives you human-friendly line numbers for error messages. I’ve used an in-memory file here so the example runs anywhere, but a real open("scores.csv") works the same way:

import io
fake = io.StringIO("name,score\nAyesha,91\nBen,seventy\n")
for line_no, line in enumerate(fake, start=1):
    if line_no == 1:
        continue  # skip the header
    name, score = line.strip().split(",")
    if not score.isdigit():
        print(f"line {line_no}: bad score {score!r}")
Enter fullscreen mode Exit fullscreen mode
line 3: bad score 'seventy'
Enter fullscreen mode Exit fullscreen mode

Reporting “line 3” rather than “row 1 after the header” makes it easy to find the problem in a text editor. For proper CSV parsing with quoted fields, use the csv module, as in my guide to reading a CSV in Python without pandas.

enumerate() in list and dict comprehensions

enumerate() fits neatly into comprehensions. Two patterns I use a lot: building a lookup from item to position, and finding the positions of items that match a test:

print({fruit: i for i, fruit in enumerate(fruits)})
print([i for i, f in enumerate(fruits) if "an" in f])
Enter fullscreen mode Exit fullscreen mode
{'apple': 0, 'banana': 1, 'cherry': 2}
[1]
Enter fullscreen mode Exit fullscreen mode

The second one returns [1] because only “banana” contains “an”. It finds every match, unlike list.index(), which stops at the first.

Every second item, and other counter tricks

Because you have the counter, you can act on every nth item with the modulo operator:

for i, f in enumerate(fruits):
    if i % 2 == 0:
        print(i, f)
Enter fullscreen mode Exit fullscreen mode
0 apple
2 cherry
Enter fullscreen mode Exit fullscreen mode

For simply taking every second item, slicing (fruits[::2]) is shorter. Use enumerate() when you also need the position, for example to print “row 3 of 10” or to add a separator before every item except the first (if i > 0:).

Numbering results on page 2 and beyond

start is great for paginated lists. If page 2 shows items 11 to 20, you want the numbers to carry on from page 1 rather than restart at 1. Work out the offset once and pass offset + 1 as start:

results = [f"item-{n}" for n in range(1, 26)]
page, per_page = 2, 10
offset = (page - 1) * per_page
for n, item in enumerate(results[offset:offset + per_page], start=offset + 1):
    print(n, item)
Enter fullscreen mode Exit fullscreen mode
11 item-11
12 item-12
...
20 item-20
Enter fullscreen mode Exit fullscreen mode

I’ve trimmed the middle lines of the output; the full run printed 11 to 20 in order. The same offset is what you’d pass to LIMIT ... OFFSET ... in SQL when fetching that page from a database.

Nested loops: rows and columns

For a grid (a list of lists), use one enumerate() per level to get row and column numbers. Here I look for every O on a small noughts-and-crosses board:

grid = [["X", "O", "X"], ["O", "X", "O"]]
for r, row in enumerate(grid):
    for c, cell in enumerate(row):
        if cell == "O":
            print(f"O at row {r}, col {c}")
Enter fullscreen mode Exit fullscreen mode
O at row 0, col 1
O at row 1, col 0
O at row 1, col 2
Enter fullscreen mode Exit fullscreen mode

Use the default start=0 here, so the numbers match grid[r][c] exactly. That makes it easy to update a cell later with grid[r][c] = "X".

Negative and non-integer start values

start can be any integer, including a negative one. It can’t be a float:

print(list(enumerate(fruits, start=-1)))
try:
    list(enumerate(fruits, start=1.5))
except TypeError as err:
    print("TypeError:", err)
Enter fullscreen mode Exit fullscreen mode
[(-1, 'apple'), (0, 'banana'), (1, 'cherry')]
TypeError: 'float' object cannot be interpreted as an integer
Enter fullscreen mode Exit fullscreen mode

Negative starts are rare, but they’re handy when your numbering needs an offset, such as counting relative to a reference row.

The list.index() trap

A common beginner bug is to call .index() inside a loop to find the current position. It returns the position of the first matching value, so it goes wrong as soon as the list has duplicates:

dupes = ["a", "b", "a"]
print([dupes.index(x) for x in dupes], [i for i, _ in enumerate(dupes)])
Enter fullscreen mode Exit fullscreen mode
[0, 1, 0] [0, 1, 2]
Enter fullscreen mode Exit fullscreen mode

The third item is at position 2, but .index() says 0. It’s also slow on big lists, because it searches from the beginning every time. enumerate() gives the correct position for free. The _ is a convention for “I don’t need this value”.

Common enumerate() mistakes

  • Swapping the order: it’s for i, item in enumerate(items), count first. for item, i in ... runs without error but gives you confusing names.
  • Forgetting to unpack: for pair in enumerate(items) gives you tuples; use pair[0] and pair[1] or unpack straight away.
  • Using the start=1 counter as an index: items[n] with start=1 skips the first item and raises IndexError on the last.
  • Reusing an exhausted enumerate object: like any iterator, it’s empty after one full pass. Call enumerate() again for a second loop.
  • Changing the list while enumerating it: appending to or removing from a list inside its own for loop shifts the positions under you, so items get skipped or processed twice. Loop over a copy (for i, x in enumerate(items[:]):) or build a new list instead.
  • Needing a running total, not a counter: enumerate() only counts items. For sums, use a separate variable or itertools.accumulate().

FAQ

How do I make Python enumerate start at 1?

Pass the start argument: for n, item in enumerate(items, start=1):. You can also write enumerate(items, 1). The first item is still items[0]; only the counter changes.

What does enumerate() return in Python?

It returns an enumerate object, a lazy iterator that yields (count, item) tuples. Wrap it in list() if you want to see all the pairs at once, for example list(enumerate(["a", "b"])) gives [(0, 'a'), (1, 'b')].

Is enumerate faster than range(len())?

For everyday loops the speed difference is small, but enumerate() is cleaner, works with any iterable (including files and generators) and avoids indexing mistakes. Choose it for readability first.

How do I use enumerate with a dictionary?

Use .items() and unpack the inner pair: for i, (key, value) in enumerate(my_dict.items(), start=1):. Looping over the dict directly gives you only the keys.

Can enumerate start at a negative number?

Yes. Any integer works, so enumerate(items, start=-1) numbers the items -1, 0, 1 and so on. A float such as 1.5 raises a TypeError.

How do I enumerate in reverse in Python?

Use reversed(list(enumerate(items))) to keep each item’s original number while looping backwards, or enumerate(reversed(items), start=1) to count upwards over the reversed items.

Does enumerate work with strings and files?

Yes. enumerate() accepts any iterable. A string yields one character at a time, and an open file yields one line at a time, which makes enumerate(f, start=1) a neat way to get line numbers.


Originally published on mfaysal.com.

I'm learning cybersecurity on TryHackMe — follow my progress at tryhackme.com/p/mfaysal.dev.

Top comments (0)