Skip to content

284. Peeking Iterator

On LeetCode ->

Problem

Wrap an iterator with peek(), which returns the next value without consuming it; preserve next() and hasNext() behavior. Calls to peek() and next() are guaranteed valid.

Values:    [1, 2, 3]
Calls:     next()  peek()  peek()  next()  next()  hasNext()
Returns:   1       2       2       2       3       False
# Below is the interface for Iterator, which is already defined for you.
#
# class Iterator:
#     def __init__(self, nums):
#
#     def hasNext(self):
#
#     def next(self):

class PeekingIterator:
    def __init__(self, iterator):
        pass

    def peek(self):
        pass

    def next(self):
        pass

    def hasNext(self):
        pass

Key trick

Cache one value and track whether it is buffered; peek() fills the cache, and next() consumes it.

Trap

Do not use the cached value's truthiness or None to detect availability: generic iterators may contain 0, False, or None.

Why is it interesting?

It adds lookahead to a forward-only interface using constant space, a technique useful in parsers and streaming algorithms.

Python solution

class PeekingIterator:
    def __init__(self, iterator):
        self.it = iterator
        self.ready = False
        self.val = None

    def peek(self):
        if not self.ready:
            if not self.it.hasNext():
                raise StopIteration
            self.val = self.it.next()
            self.ready = True
        return self.val

    def next(self):
        val = self.peek()
        self.ready = False
        self.val = None  # Release the consumed value's reference.
        return val

    def hasNext(self):
        return self.ready or self.it.hasNext()

Time: \(O(1)\) per operation, assuming constant-time underlying operations.

Extra space: \(O(1)\).

Comment on my solution

Your solution is correct, uses \(O(1)\) time per operation and \(O(1)\) extra space, and already supports arbitrary value types through ready.

Your hasNext() performs lookahead and may advance the underlying iterator, but preserves the wrapper's logical position by caching the value; this is valid. The version above delays that read until peek() or next().

# WORKS
class PeekingIterator:
    def __init__(self, iterator):
        self.it = iterator
        self.ready = False
        self.val = None

    def peek(self):
        if not self.hasNext():
            raise StopIteration
        return self.val

    def next(self):
        if not self.hasNext():
            raise StopIteration
        self.ready = False
        return self.val

    def hasNext(self):
        if self.ready:
            return True
        if not self.it.hasNext():
            return False
        self.ready = True
        self.val = self.it.next()
        return True