The Python next() function is the mechanism that retrieves items from an iterator one at a time, called behind the scenes by every for loop you write. While the for loop hides next() behind clean syntax, every value that a for loop assigns to its loop variable arrives through a next() call. Understanding next() explicitly gives you fine-grained control over iteration: you can peek at the next item without consuming it in a loop body, you can skip items conditionally, you can interleave items from multiple iterators, and you can build state machines that consume inputs at their own pace rather than at the steady rhythm of a for loop.
The function's signature is straightforward: next(iterator) advances the iterator and returns the next item, or raises StopIteration if the iterator is exhausted. The optional second argument, next(iterator, default), changes this behaviour so that default is returned instead of raising StopIteration. Both forms are built into the Python runtime and work with any object that implements the iterator protocol, including built-in collection iterators, generator objects, file iterators, and custom iterators you create yourself.
How next() works internally
When you call next(iterator), Python calls the iterator's next() special method and returns whatever that method returns. If next() raises StopIteration, next() propagates that exception to the caller. If next() raises any other exception, next() propagates that exception as well. The next() function does not add any logic beyond dispatching to next() and handling the optional default value.
This direct relationship between next() and next() means that the behaviour of next() is entirely determined by the iterator you pass to it. A list iterator's next() returns the next element by index. A dictionary iterator's next() returns the next key in insertion order. A generator's next() resumes the generator function until the next yield statement. A file iterator's next() reads the next line from disk. Each iterator type defines its own next() behaviour, and next() is the uniform interface for invoking it.
The two-argument form of next() is implemented as a simple try-except inside the C implementation of the built-in function. When a default value is provided, next() catches StopIteration from next() and returns the default value instead of propagating the exception. All other exceptions, including StopAsyncIteration, are propagated normally. The default value can be any Python object, including None, a sentinel value, or a computed default. There is no restriction on the type of the default, though using a value that could also be a valid item from the iterator can lead to ambiguity.
Manual iteration with next()
The most direct use of next() is to consume an iterator manually, one item at a time, outside of a for loop. You first obtain an iterator by calling Python's iter() function on an iterable, and then you call next() repeatedly until StopIteration tells you the iterator is empty:
numbers = [10, 20, 30]
it = iter(numbers)
print(next(it))
print(next(it))
print(next(it))This prints 10, 20, and 30, each on its own line. A fourth call to next(it) would raise StopIteration because the iterator has been fully consumed. This pattern is the manual equivalent of a for loop, and while it is more verbose, it gives you control over when each item is fetched. You can intersperse other logic between next() calls, skip items by calling next() without using the return value, or stop consuming items before the iterator is exhausted.
The two-argument form of next() is especially useful when you want to consume items from an iterator that might be empty, and you prefer a default value over an exception. Read the first line of a file, or use a default string if the file is empty:
with open("data.txt") as f:
first_line = next(f, "").strip()If data.txt contains at least one line, first_line holds the stripped version of that line. If data.txt is empty, first_line is an empty string. Without the default argument, an empty file would cause StopIteration, and you would need a try-except block to handle it. The default argument collapses the exceptional case into a simple expression.
Using next() to peek and skip
A common pattern in data processing is peeking at the next item without consuming it when you need to decide whether to process it or skip it. Python iterators do not have a built-in peek operation, but you can simulate it by calling next() and storing the result. If you decide not to consume the item, you can push it onto a buffer for later processing.
A simpler approach when you only need to skip items conditionally is to call next() without using its return value. For example, to skip the first line of a CSV file that contains a header row, you can call next() once before entering a loop over the remaining lines:
with open("data.csv") as f:
header = next(f)
for line in f:
print(line.strip())The header line is consumed by next() and stored in the header variable, while the for loop processes every subsequent line. The file iterator continues from where next() left off, so the loop body never sees the header. This pattern is cleaner than a flag variable that tracks whether the current line is the first one, and it generalizes to skipping any number of initial items.
Interleaving multiple iterators with next()
Because next() gives you explicit control over which iterator advances when, you can interleave items from multiple iterators in ways that zip() and other built-in tools cannot express. Round-robin processing, where you take one item from each iterator in turn, requires manual next() calls:
primary = iter([1, 2, 3])
secondary = iter(["a", "b", "c"])
while True:
p = next(primary, None)
s = next(secondary, None)
if p is None and s is None:
break
if p is not None:
print(f"primary: {p}")
if s is not None:
print(f"secondary: {s}")This code interleaves items from both iterators, printing one from primary then one from secondary, and continuing until both are exhausted. The built-in zip() function pairs corresponding items and stops at the shortest iterator, which is different from round-robin interleaving.
A more practical example is merging two sorted iterators into a single sorted output stream. You peek at the next item from each iterator, compare them, and consume the smaller one:
def merge_sorted(left, right):
li, ri = iter(left), iter(right)
lv, rv = next(li, None), next(ri, None)
while lv is not None or rv is not None:
if rv is None or (lv is not None and lv <= rv):
yield lv
lv = next(li, None)
else:
yield rv
rv = next(ri, None)This generator uses next() with a default of None to peek at the current front of each sorted iterator, yielding the smaller value and advancing the iterator that provided it until both are exhausted.
Building state machines with next()
Manual iteration with next() enables a programming style where a function consumes items from an iterator at its own pace, processing some items, skipping others, and changing behaviour based on what it sees. This is essentially a state machine driven by an input stream, and it is a common pattern in parsers, protocol handlers, and event processors.
Consider a simple parser that reads tokens from an iterator and groups them into records separated by a special delimiter token. The parser uses next() to consume tokens and builds records until it encounters a delimiter:
def group_records(tokens, delimiter):
it = iter(tokens)
current = []
while (token := next(it, None)) is not None:
if token == delimiter:
if current:
yield current
current = []
else:
current.append(token)
if current:
yield currentThe assignment expression in the while condition fetches the next token and stops the loop when next() returns the None default, meaning the iterator is exhausted. The function yields ["a", "b"], then ["c", "d"], and finally ["e"] when given tokens ["a", "b", "---", "c", "d", "---", "e"] with "---" as the delimiter. The delimiter is consumed but never appears in any output record.
This pattern scales to more complex state machines. A JSON tokenizer, an HTTP request parser, or a byte-stream protocol decoder can all be built around a central iterator and a loop that calls next() to advance through the input. The iterator provides a uniform interface that decouples the parsing logic from the data source, so the same parser can consume tokens from a list, a file, a network socket, or a generator without modification.
The relationship between next() and for loops
Understanding next() clarifies what for loops actually do. A for loop is syntactic sugar for a specific pattern: it calls iter() on the collection to get an iterator, then wraps repeated next() calls in a try-except that catches StopIteration to break out of the loop. The for loop version is shorter, less error-prone, and handles cleanup correctly when the loop body raises an exception. But knowing the explicit mechanism helps you debug iteration problems. If a for loop runs zero iterations when you expected data, the first next() call raised StopIteration because the iterator was already exhausted. If a for loop raises TypeError, iter() failed because the object is not iterable.
This equivalence also means that any object you can pass to next() can be used in a for loop. If you have written a custom iterator class that implements next() correctly, it works in for loops automatically. The for loop does not care whether the iterator is a built-in list iterator or a hand-written class, because both respond to the same iter() and next() calls in the same way.
Handling StopIteration in practice
The choice between catching StopIteration and using a default value with next() depends on how you plan to handle exhaustion. If exhaustion is a normal condition and you have a natural default value that cannot be confused with a valid item, the two-argument form next(it, default) is shorter and clearer. If exhaustion is unexpected and should trigger different logic than a valid item, catching StopIteration with try-except gives you room to log a warning, raise a custom exception, or switch to a fallback data source.
One subtlety to remember is that StopIteration raised inside a generator function is converted to RuntimeError in Python 3.7 and later. If you are writing a generator that calls next() on a sub-iterator, you should either catch StopIteration inside the generator or use the two-argument form with a default value to avoid the RuntimeError conversion. The conversion was introduced to prevent bugs where a StopIteration from a nested next() call silently terminates the outer generator.
Another practical consideration is performance. In tight loops that process millions of items, the try-except overhead of catching StopIteration on every exhaustion can be measurable. The two-argument form of next() avoids this overhead by handling the StopIteration internally in C code rather than propagating it to a Python-level except block. For most applications the difference is negligible, but in the innermost loop of a data processing pipeline, using next(it, sentinel) is the more efficient choice.
Rune AI
Key Insights
- next(iterator) calls the iterator's next() method and returns the next item, or raises StopIteration when the iterator is exhausted.
- next(iterator, default) returns default instead of raising StopIteration, which is cleaner than try-except for simple sentinel handling.
- You must call next() on an iterator, not on an iterable like a list; use iter() first to obtain an iterator from any iterable.
- Every for loop is a thin wrapper around repeated next() calls that terminate when StopIteration is raised.
- Manual next() calls are useful for peeking, skipping, interleaving multiple iterators, and building state machines that consume inputs at their own pace.
Frequently Asked Questions
What does the next() function do in Python?
How do I avoid StopIteration when using next()?
Can I call next() on a list directly?
Conclusion
The next() function is the manual interface to Python's iteration system. Every for loop calls it behind the scenes, and understanding it gives you precise control over how and when items are consumed from iterators. Combined with iter(), it lets you write custom iteration logic that handles exhaustion exactly the way your program needs.
More in this topic
Create Custom Iterators in Python
Learn how to build your own iterators in Python by implementing the __iter__() and __next__() methods, with practical examples and reusable patterns.
Python Iterator Protocol Explained
Learn how Python's iterator protocol works with the __iter__() and __next__() methods, and understand the contract every iterator must follow.
Python Iterables and Iterators Explained
Learn what iterables and iterators are in Python, how they power for loops under the hood, and why understanding them unlocks cleaner data processing patterns.