skip to content

What does next(it, default) return when the iterator is already exhausted?

level: juniorimportance: must knowfreq 45%

answer

  1. Exhaustion need not be an error
  2. A second positional argument
  3. Replaces a try/except around one call
  4. Only one exception type is absorbed

basics

~10 s

It returns the second argument. With only one argument, next raises StopIteration on an exhausted iterator; supplying a fallback turns exhaustion into an ordinary return value. Any other exception the iterator raises still propagates.

solid answer

~40 s

`next(it)` advances an iterator one step and raises `StopIteration` when there is nothing left. The two-argument form, `next(it, default)`, catches only that exhaustion and returns `default` instead, so it is an exact replacement for a `try: next(it) except StopIteration: ...` block around a single call. Two things trip people up. First, the argument must be an *iterator* (something with `__next__`), not merely an iterable - `next([1, 2], None)` raises `TypeError`, because a list has no `__next__`. Second, the default only absorbs `StopIteration`: if the iterator raises `ValueError` while producing an item, that still escapes. The default is also an ordinary argument, so it is evaluated on every call whether or not it is used, and it cannot be passed by keyword.

code

python · 4 lines
python
lines = iter(["alice: hi", "bob: hey"])
print(next(lines, None))
print(next(lines, None))
print(next(lines, None))

go deeper

for a junior

Be ready to state that the second argument is returned instead of raising StopIteration, and that the first argument must be an iterator such as a generator or the result of iter(), not a bare list.

for a middle

Explain the exact equivalence with a try/except StopIteration block, and that only StopIteration is absorbed. Be able to show why None is an unsafe default when the data itself may be None or falsy.

for a senior

Show the judgement calls: eager evaluation of the default in hot loops, a module-level object() sentinel over None, and never letting a fallback quietly mask a parse or I/O failure inside the iterator.

for a principal

Own the convention. Decide when a codebase should standardise on a shared MISSING sentinel and where 'absent' should be a real return type instead, so that absence is expressed once rather than re-invented per call site.

`next()` is the builtin that pulls exactly one item out of an iterator. Its single-argument form, `next(it)`, either returns the next item or raises `StopIteration` to signal that the iterator is finished. The two-argument form, `next(it, default)`, keeps the first behaviour and replaces the second: exhaustion returns `default` rather than raising. ### The exact contract The first argument must be an **iterator**, not just an iterable. An iterable is anything you can start iterating (a list, a `str`, a `dict`); an iterator is the stateful cursor with a `__next__` method that a fresh call to `iter()` hands back. `next(["a", "b"], None)` therefore raises `TypeError`, not `None` - the default cannot rescue a wrong argument type. Passing `iter(...)` first is the fix. The default absorbs `StopIteration` **and nothing else**. If the underlying iterator raises `ValueError` because a line will not parse, or `OSError` because the file went away mid-read, that exception travels straight out through `next()`. This is the property that makes the form safe: it is a targeted replacement for one `except StopIteration:` clause, not a blanket error swallower. The two-argument call is exactly equivalent to: ```python try: value = next(it) except StopIteration: value = default ``` The builtin version is shorter, is one expression rather than four statements, and - importantly for readability - keeps the fallback visually next to the call it belongs to. In a chat-transcript archiver that reads a header line and then an optional body line, the difference is a nested `try` block per record versus one call per record. ### Details that show up in review **The default is evaluated eagerly.** It is an ordinary positional argument, so `next(it, expensive_probe())` calls `expensive_probe()` on *every* iteration step, exhausted or not. When the fallback is costly, keep the `try/except`, or make the default a cheap sentinel and compute the real fallback afterwards. **There is no keyword form.** `next` takes its arguments positionally only; `next(it, default=None)` raises `TypeError`. Candidates who have only ever seen the keyword-friendly builtins are surprised by this. **It does not rewind or peek.** Each call consumes an item permanently. If you call `next(it, None)` to "look" at the next value, that value is gone from the iterator; a real peek means holding the item yourself and threading it back into whatever consumes the iterator afterwards. **`None` is not always a safe default.** If the iterator can legitimately yield `None` - or `0`, or `""`, or an empty list - then a `None` default is indistinguishable from a real item under a truthiness check. The standard fix is a private sentinel created with `MISSING = object()`, compared with `is`. Every object created that way is unique, so no data value can ever collide with it. **A generator is an iterator.** Calling a generator function returns a generator object that already has `__next__`, so it can be passed to `next()` directly with no `iter()` wrapper. That is what makes the two-argument form the backbone of the first-match search idiom. ### Why interviewers ask it It is a small, checkable piece of the iteration model that reveals whether a candidate distinguishes an iterable from an iterator, and whether they understand that `StopIteration` is a *control-flow signal* rather than an error condition. The signal is normally invisible because a `for` loop consumes it, and the two-argument `next()` is the other place the language lets you handle it declaratively. A candidate who says "the default suppresses errors" has the wrong mental model and will eventually write a call that hides a genuine parse failure behind a fallback value. ### Where it earns its place Reading a single optional item from a stream - one header line, one continuation record, the first line of a file whose format is locale-dependent and may be empty - is where the two-argument call is at its clearest. It also composes with lazy sources: because `next()` pulls exactly one item, a generator behind it does exactly one step of work, which matters when each step is a parse, a network read, or a database round trip.

  • Why does next([1, 2, 3], 0) raise TypeError rather than returning 1?
    Because a list is an iterable, not an iterator: it has `__iter__` but no `__next__`, and `next()` requires the latter. The default only covers exhaustion, so it cannot rescue an argument of the wrong kind. Wrapping the list - `next(iter([1, 2, 3]), 0)` - returns 1. Each call to `iter()` produces a fresh cursor, so building one per call restarts the sequence every time, which is usually not what you want inside a loop.
  • When is None a poor choice of default?
    Whenever the iterator can legitimately produce a falsy or `None` item. `next(it, None)` then gives the same answer for "the next value is None" and "there was no next value", and a `if value:` check conflates 0 and the empty string with exhaustion too. The fix is a unique sentinel: `MISSING = object()` at module level, then `if value is MISSING:`. Nothing else in the program can compare identical to it.
  • Is there any cost to writing next(it, compute_fallback())?
    Yes - `compute_fallback()` runs on every call, because the default is an ordinary positional argument evaluated before `next()` is entered, not lazily on exhaustion. In a hot loop that turns a rare fallback into a per-iteration cost. Use a cheap sentinel and compute the real fallback only when the sentinel comes back, or keep an explicit `try/except StopIteration` where the recovery path is expensive.

Asking a vending machine for one more can: with one argument it sets off an alarm when the slot is empty; with a default it just hands you a card that says "empty" and you carry on.

saying these in an interview costs you the question

  • Says the default suppresses any exception from the iterator
  • Calls next() on a list and expects the first item
  • Thinks next(it, default) peeks without consuming
  • Believes the default is evaluated lazily only on exhaustion
  • Uses None as the default for an iterator that can yield None
  • Tries to pass the fallback as a keyword argument

context