iter() and next() Functions Complexity¶
The iter() function creates an iterator object from an iterable, and next() retrieves the next item from an iterator.
Complexity Reference¶
| Operation | Time | Space | Notes |
|---|---|---|---|
iter(iterable) |
O(1) | O(1) | Create iterator from iterable |
iter(callable, sentinel) |
O(1) | O(1) | Create callable iterator |
next(iterator) |
O(1)* | O(1) | Get next item |
next(iterator, default) |
O(1)* | O(1) | Get next or default |
*O(1) for built-in types; may vary for custom iterators (e.g., filter iterator is O(k) where k items are skipped)
Creating Iterators¶
Basic Iterator Creation¶
# Create iterator from list - O(1)
lst = [1, 2, 3, 4, 5]
it = iter(lst) # O(1) - returns list iterator
# Create iterator from string - O(1)
s = "hello"
it = iter(s) # O(1) - returns string iterator
# Create iterator from dict - O(1)
d = {'a': 1, 'b': 2}
it = iter(d) # O(1) - returns dict_keyiterator
# Create iterator from set - O(1)
st = {1, 2, 3}
it = iter(st) # O(1) - returns set iterator
Iterating Over Sequences¶
# Manual iteration using iter() and next() - O(1) per item
lst = [10, 20, 30]
iterator = iter(lst)
while True:
try:
item = next(iterator) # O(1) per call
print(item)
except StopIteration:
break
# Equivalent to for loop
for item in lst: # for loop internally uses iter() and next()
print(item)
Callable Iterator Pattern¶
Creating Iterator from Callable¶
# Create iterator from callable with sentinel - O(1)
def read_line():
"""Simulate reading from a file"""
# Would read actual file in real scenario
pass
# Create iterator that calls function until sentinel reached
iterator = iter(read_line, "EOF") # O(1)
# Get items until sentinel - O(1) per next() call
item = next(iterator) # O(1)
Real-world Example: File Reading¶
# Read file without loading all in memory - O(1) per line
with open('largefile.txt') as f:
# Create iterator from lambda until empty string
iterator = iter(lambda: f.read(1024), '') # O(1) to create
for chunk in iterator: # O(n) to iterate, O(1) per chunk
process(chunk) # O(1)
Using next()¶
Basic next() Usage¶
# Get next item - O(1)
lst = [10, 20, 30]
it = iter(lst)
item1 = next(it) # 10 - O(1)
item2 = next(it) # 20 - O(1)
item3 = next(it) # 30 - O(1)
# next() after exhaustion raises StopIteration
try:
item4 = next(it) # Raises StopIteration
except StopIteration:
print("Iterator exhausted")
Using Default Value¶
# Use default if iterator exhausted - O(1)
it = iter([1, 2, 3])
next(it) # 1
next(it) # 2
next(it) # 3
item = next(it, "END") # "END" - returns default instead of raising
# Useful for safe iteration
def safe_next(iterator, default=None):
return next(iterator, default) # O(1)
Custom Iterators¶
Implementing Iterator Protocol¶
# Custom iterator class - O(1) per operation
class CountUp:
def __init__(self, max):
self.max = max
self.current = 0
def __iter__(self):
return self # O(1) - return iterator itself
def __next__(self):
if self.current < self.max:
self.current += 1
return self.current # O(1)
else:
raise StopIteration
# Usage
counter = CountUp(3)
it = iter(counter) # O(1)
item1 = next(it) # 1 - O(1)
item2 = next(it) # 2 - O(1)
item3 = next(it) # 3 - O(1)
Custom Iterable with Iterator¶
# Iterable that creates fresh iterator - O(1)
class Range:
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self):
return RangeIterator(self.start, self.stop) # O(1)
class RangeIterator:
def __init__(self, start, stop):
self.current = start
self.stop = stop
def __iter__(self):
return self # O(1)
def __next__(self):
if self.current < self.stop:
value = self.current
self.current += 1
return value # O(1)
else:
raise StopIteration
# Usage
r = Range(0, 3)
it1 = iter(r) # O(1) - fresh iterator
it2 = iter(r) # O(1) - another fresh iterator
Generator Functions¶
Generators use iter() and next()¶
# Generator function returns iterator - O(1)
def count_up(max):
i = 0
while i < max:
yield i # Yields value, pauses execution
i += 1
# Create iterator from generator - O(1)
gen = count_up(3) # O(1) - returns generator object
# Iterate using next() - O(1) per call
item1 = next(gen) # 0 - O(1), resumes at yield
item2 = next(gen) # 1 - O(1)
item3 = next(gen) # 2 - O(1)
# For loop equivalent
for item in count_up(3): # Uses iter() and next() internally
print(item)
Generator Expression¶
# Generator expression returns iterator - O(1)
gen = (x**2 for x in range(5)) # O(1) - lazy evaluation
# Iterate using next() - O(1) per item
item1 = next(gen) # 0 - O(1)
item2 = next(gen) # 1 - O(1)
item3 = next(gen) # 4 - O(1)
# For loop
for item in (x**2 for x in range(5)): # O(1) per iteration
print(item)
Common Patterns¶
Converting to List (Forces Evaluation)¶
# Create iterator - O(1)
it = iter([1, 2, 3, 4, 5])
# Convert to list - forces all iterations O(n)
lst = list(it) # O(5) - calls next() 5 times
# Iterator is now exhausted
try:
next(it) # Raises StopIteration
except StopIteration:
print("Exhausted")
Chaining Multiple Iterators¶
# Create multiple iterators - O(1) each
it1 = iter([1, 2, 3])
it2 = iter([4, 5, 6])
it3 = iter([7, 8, 9])
# Iterate through each - O(n) total
for it in [it1, it2, it3]:
while True:
try:
item = next(it) # O(1) per call
print(item)
except StopIteration:
break
Zip with Iterators¶
# Create iterators - O(1) each
it1 = iter([1, 2, 3])
it2 = iter(['a', 'b', 'c'])
# zip pairs items from iterators - O(1) to create
paired = zip(it1, it2)
# Iterate - O(1) per pair
for num, letter in paired: # O(n) total
print(num, letter)
# Equivalent manual iteration
it1 = iter([1, 2, 3])
it2 = iter(['a', 'b', 'c'])
while True:
try:
item1 = next(it1) # O(1)
item2 = next(it2) # O(1)
print(item1, item2)
except StopIteration:
break
Performance Considerations¶
Iterator vs List Memory¶
# Iterator - constant memory O(1)
def infinite_count():
i = 0
while True:
yield i
i += 1
gen = infinite_count() # O(1) memory
item1 = next(gen) # O(1)
item2 = next(gen) # O(1)
# List - linear memory O(n)
lst = list(range(1000000)) # O(n) memory!
Lazy Evaluation Benefits¶
import itertools
# Create iterator (lazy) - O(1)
def get_data():
for i in range(1000000):
yield i
# Don't load all in memory
for item in get_data(): # O(1) per item, O(1) total memory
if item > 100:
break # Can stop early without processing all
# Avoid with list
data = list(range(1000000)) # O(n) memory - loaded all upfront!
for item in data:
if item > 100:
break
Efficient Filtering¶
# Generator with filter (lazy) - O(1) per item
def filtered_data():
for i in range(1000000):
if i % 2 == 0:
yield i
# Process only needed items
for item in filtered_data(): # O(1) per item, stop early
print(item)
if item > 100:
break
# Vs materializing to list
data = [i for i in range(1000000) if i % 2 == 0] # O(n) memory
for item in data:
print(item)
if item > 100:
break
Built-in Iterator Functions¶
map() Returns Iterator¶
# map creates iterator (lazy) - O(1)
def square(x):
return x ** 2
it = map(square, [1, 2, 3, 4, 5]) # O(1)
# Iterate - O(1) per item
item1 = next(it) # 1 - O(1)
item2 = next(it) # 4 - O(1)
item3 = next(it) # 9 - O(1)
filter() Returns Iterator¶
# filter creates iterator (lazy) - O(1)
def is_even(x):
return x % 2 == 0
it = filter(is_even, [1, 2, 3, 4, 5]) # O(1)
# Iterate - O(1) per item
for item in it: # O(n) total, O(1) per item
print(item)
enumerate() Returns Iterator¶
# enumerate creates iterator - O(1)
it = enumerate(['a', 'b', 'c']) # O(1)
# Iterate - O(1) per item
item1 = next(it) # (0, 'a') - O(1)
item2 = next(it) # (1, 'b') - O(1)
item3 = next(it) # (2, 'c') - O(1)
Version Notes¶
- Python 2.x:
range()returns list, not iterator (usexrange()for iterator) - Python 3.x:
range()returns iterator-like object,map()andfilter()return iterators - All versions: Iterator protocol with
__iter__()and__next__()(Python 3) ornext()(Python 2)
Related Functions¶
- zip() - Iterate multiple sequences
- map() - Transform iterator items
- filter() - Filter iterator items
- enumerate() - Get index and value
- range() - Iterator-like sequence
Best Practices¶
✅ Do:
- Use
iter()andnext()for custom iterators - Use generators for memory-efficient iteration
- Use default value in
next()to avoid exceptions - Use
forloops (they useiter()andnext()internally)
❌ Avoid:
- Calling
next()without handlingStopIteration - Creating iterators you won't use (lazy evaluation wasted)
- Assuming you can iterate an iterator twice (exhausts on first pass)
- Using
next()in tight loops without performance profiling