What does Python's enumerate() yield, and what does its start argument change?
answer
- It wraps, it does not copy
- Two things come out per step
- The counter is the first element
- One argument moves only the numbering
- start=1 is not an index
basics
~10 senumerate() wraps any iterable and lazily yields (count, item) two-tuples. The start argument sets the first count, so start=1 numbers items from one. It shifts only the counter, never which items come out.
solid answer
~40 s`enumerate(iterable, start=0)` returns an **enumerate object**, which is an iterator: each step pulls one item from the underlying iterable and yields a two-tuple of `(count, item)`. In a `for` header you unpack it directly, as in `for i, name in enumerate(names):`. The `start` argument only sets the value the counter begins at — `enumerate(names, start=1)` still yields every element of `names`, beginning at count 1, so it is for human-facing numbering, not for skipping input. Because it is lazy it works on files, generators and any other one-pass iterable where indexing is impossible, and because it is an iterator it is exhausted after one full pass. There is no step argument; the counter always increments by one.
code
python · 7 linesnames = ["ada", "grace", "alan"]
for rank, name in enumerate(names, start=1):
print(rank, name)
print(list(enumerate(names)))
print(list(enumerate(names, start=1)))go deeper
Be ready to write the loop header from memory and say out loud that the counter comes first in the tuple. Knowing that the second argument only changes the starting number is the whole question at this level.
Explain that the call returns a lazy iterator rather than a list, so it works on files and generators and is exhausted after one pass. Name the off-by-one that follows from indexing with a counter that started at one.
Show judgement about where the numbering is used: display numbering with start=1, positional logic with start=0, and never both from one name. Mention materialising with list() when the pairs must be walked twice.
Own the readability argument in a style guide: hand-maintained counters drift inside branches and early exits, and a lazy wrapper keeps memory flat over streams. Decide when a team should require it and when an explicit index is genuinely clearer.
## What the built-in actually returns `enumerate` is a built-in callable with the signature `enumerate(iterable, start=0)`. Calling it does **not** produce a list and does not consume the input; it constructs an **enumerate object**, which is itself an iterator. Each time that iterator is advanced, it pulls exactly one item from the wrapped iterable and yields a two-element tuple: the current count first, the item second. When the wrapped iterable is exhausted, the enumerate object raises `StopIteration` in the normal way and the surrounding `for` loop ends. The tuple order matters and is a common slip: it is `(count, item)`, not `(item, count)`. In practice you rarely see the tuple, because the `for` statement's target list unpacks it for you: ```python for position, name in enumerate(["ada", "grace", "alan"]): print(position, name) ``` If the items are themselves tuples, nest the target: `for i, (key, value) in enumerate(mapping.items()):`. ## The start argument `start` is the value the counter takes on the first yielded tuple; it defaults to `0`. `enumerate(rows, start=1)` yields `(1, rows[0])`, `(2, rows[1])`, and so on. It may be passed positionally (`enumerate(rows, 1)`) or by keyword, and it accepts any integer, including negative values. The crucial point — and the one an interviewer is usually probing — is that `start` changes **only the counter**. It does not skip an element, it does not slice the input, and it does not alter the items. `enumerate(rows, start=1)` and `enumerate(rows)` walk exactly the same elements in exactly the same order; only the numbers differ. This makes `start=1` the right tool for human-facing output such as report line numbers or ranked output, where people count from one. The corollary is the off-by-one trap: once you have chosen `start=1`, the counter is no longer a valid index into the original sequence. Code that writes `rows[position]` inside a loop opened with `start=1` reads the wrong element and, on the last pass, raises `IndexError`. If you need both a display number and an index, either keep `start=0` and add one at the point of display, or bind two names. There is no `step` parameter. The counter always advances by exactly one per item. Anyone who wants every second item is describing slicing or a different iteration tool, not `enumerate`. ## Why laziness matters Because `enumerate` is an iterator wrapper rather than a sequence builder, it composes with things that have no length and no indexing at all: an open text file iterated line by line, a generator, a network reader, an object that produces items one at a time and cannot rewind. Numbering those with subscripts is impossible; numbering them with `enumerate` is one word. It also means no intermediate list is built, so memory stays flat no matter how long the stream is. The other side of laziness is single use. An enumerate object is consumed as you walk it; a second `for` over the same object yields nothing, because both the counter and the underlying iterable are already exhausted. If you genuinely need the pairs twice, materialise them once with `list(enumerate(items))` — that gives a real list of tuples you can iterate repeatedly, index and slice. ## What gets enumerated `enumerate` numbers whatever the iterable yields — nothing more. Over a `dict` it yields `(count, key)`, because iterating a dict yields keys; to number key/value pairs you enumerate `mapping.items()`. Over a `str` it yields `(count, character)`. Over a file object it yields `(count, line)`, with the line's trailing newline still attached. Over a `set` it yields the elements in the set's iteration order, which is not insertion order and should not be relied on for stable numbering. ## Reading the result correctly A quick self-check that catches most misuse: the counter is *produced by* `enumerate`, and the item is produced by the underlying iterable; they are independent. `start` touches the first. Slicing, filtering or reversing the input touches the second. Confusing the two is what produces reports whose numbering restarts, skips or runs one ahead of the data it labels. One last detail worth knowing: iterating `enumerate(seq)` avoids the repeated subscript lookups that a manual counter plus indexing would perform, and it removes the hand-maintained `i += 1` line that is easy to forget inside a branch. That readability argument, not raw speed, is the reason style guides reach for it by default.
- You open a loop with enumerate(rows, start=1) and index rows with the counter inside it. What happens?Every read is off by one, and the final pass raises `IndexError` because the counter reaches `len(rows)`. The `start` argument shifts only the number, so it stops being a usable index the moment it is not zero. Either keep `start=0` and add one where you print, or bind the display number separately from the index.
- Can you use enumerate on an object that has no length and cannot be indexed?Yes, and that is one of its main advantages. `enumerate` only requires an iterable, so it numbers generators, open file objects and other one-pass streams, pulling one item at a time and keeping memory flat. Nothing about it needs `__len__` or `__getitem__`.
- How do you number key/value pairs of a dict with enumerate?Enumerate the view, not the mapping: `for i, (key, value) in enumerate(mapping.items()):`. Iterating a `dict` directly yields keys only, so `enumerate(mapping)` gives `(count, key)` tuples. The nested target list unpacks the inner two-tuple from `items()` in the same header.
Think of a clerk walking a queue and calling out a number as each person passes. Changing the number they start from renames everyone's ticket; it never removes anyone from the queue.
saying these in an interview costs you the question
- Says enumerate returns a list of tuples
- Thinks start=1 skips the first element
- Puts the item before the counter when unpacking
- Believes enumerate needs a sequence with a length
- Indexes the original list with a start=1 counter
- Claims enumerate takes a step argument