Blog / Coding tips

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

Cover image for 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)
0 apple
1 banana
2 cherry

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}")
1. apple
2. banana
3. cherry

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)))
enumerate (0, 'apple') [(1, 'banana'), (2, 'cherry')]
[(1, 'a'), (2, 'b'), (3, 'c')]

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

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])
0 apple
1 banana
2 cherry

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)
[12.0, 15.0, 9.6]

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)
1 Ayesha 91
2 Ben 73

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)
1 Ayesha 91
2 Chloe 88
3 Ben 73

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)
1 Ayesha 91
2 Ben 73
3 Chloe 88

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)
3 cherry
2 banana
1 apple

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)
1 cherry
2 banana
3 apple

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}")
line 3: bad score 'seventy'

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])
{'apple': 0, 'banana': 1, 'cherry': 2}
[1]

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)
0 apple
2 cherry

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)
11 item-11
12 item-12
...
20 item-20

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}")
O at row 0, col 1
O at row 1, col 0
O at row 1, col 2

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)
[(-1, 'apple'), (0, 'banana'), (1, 'cherry')]
TypeError: 'float' object cannot be interpreted as an integer

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)])
[0, 1, 0] [0, 1, 2]

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.

// note

How to read this note.

This is a learning note from studying the web. It is one small topic, written so I can remember it. It is not a course and not a claim that I have finished the subject.

If a sentence is wrong, say so from the contact page and name this title. Drafts never appear here. Related notes, when they exist, are other published posts, and the same sample rule applies to each of them.

Related posts

What Is UAC in Windows? User Account Control Explained for Beginners
Coding tips

What Is UAC in Windows? User Account Control Explained for Beginners

UAC (User Account Control) is the Windows feature that runs your apps with standard rights and asks before anything gets admin power. Here’s how the two tokens, the dimmed secure desktop, the four slider levels and the registry settings fit together.

October 11, 2026 · 13 min read · 1 view

NTFS vs FAT32 vs exFAT: Which Format Should You Use? (Windows 2026)
Coding tips

NTFS vs FAT32 vs exFAT: Which Format Should You Use? (Windows 2026)

Use NTFS for internal Windows drives, exFAT for USB sticks and SD cards you share with a Mac, and FAT32 only for small or old devices. Here are the real limits (4 GB files, 32 GB format cap), the security differences and the commands.

October 11, 2026 · 12 min read · 0 views

What Is %windir%? Windows Environment Variables Explained (With a List)
Coding tips

What Is %windir%? Windows Environment Variables Explained (With a List)

%windir% is an environment variable that points to the Windows folder, usually C:\Windows. Here’s what the common variables mean, the difference between user and system variables, set vs setx, PowerShell’s $Env: syntax and how PATH finds programs.

October 11, 2026 · 11 min read · 1 view