Katkat

Implement a Stepped Iterator

BridgeSoftware Engineer · Technical Screen

Last updated

Design and implement a stepped iterator class that wraps an underlying iterator or sequence, advancing by a configurable step size on each call. This tests iterator design, state management, invariant preservation, and edge-case handling.

Problem Statement

Implement a stepped iterator class SteppedIterator that wraps an underlying iterator (or iterable sequence) and yields elements at a fixed positive step interval k.

When initialized with an underlying iterator and a positive integer step = k:

  • The first call to next() returns the first available element from the underlying iterator (index 0).
  • Each subsequent call to next() advances past k - 1 elements and returns the k-th element (indices k, 2k, 3k, ...).
  • Calling hasNext() returns True if there is at least one more stepped element available to consume, and False otherwise. Calling hasNext() multiple times consecutively without calling next() must be idempotent and must not alter the iteration state.
  • If next() is called when no elements remain, it should raise a StopIteration exception.

Your implementation should work with any standard iterator (which may only support single forward passes without random access or length introspection) using O(1) auxiliary space beyond buffering the next stepped item.

Class Interface

from typing import Any, Iterator

class SteppedIterator:
    def __init__(self, iterator: Iterator[Any], step: int):
        """
        Initializes the stepped iterator.
        :param iterator: The underlying iterator to wrap.
        :param step: A positive integer indicating step size (k >= 1).
        """
        pass

    def hasNext(self) -> bool:
        """
        Returns True if the iteration has more elements.
        """
        pass

    def next(self) -> Any:
        """
        Returns the next element in the stepped iteration.
        Raises StopIteration if no further elements exist.
        """
        pass

Constraints

  • 1 <= step <= 10^9
  • The underlying iterator may be infinite, empty, or contain up to 10^6 elements.
  • Time complexity per call to next() should be O(step) operations on the underlying iterator.
  • Space complexity must be O(1) auxiliary memory.

Examples

Example 1

source = iter([10, 20, 30, 40, 50, 60, 70])
it = SteppedIterator(source, step=2)

assert it.hasNext() == True
assert it.next() == 10    # index 0
assert it.hasNext() == True
assert it.next() == 30    # index 2
assert it.next() == 50    # index 4
assert it.next() == 70    # index 6
assert it.hasNext() == False
# Calling it.next() now raises StopIteration

Example 2

source = iter([1, 2, 3, 4, 5])
it = SteppedIterator(source, step=3)

assert it.next() == 1    # index 0
assert it.next() == 4    # index 3
assert it.hasNext() == False

Edge Cases to Consider

  • step == 1: Acts identically to the underlying iterator.
  • Empty underlying iterator: hasNext() returns False immediately, and next() raises StopIteration.
  • Fewer elements remaining than step: Ensure remaining elements are exhausted cleanly without indexing errors.
  • Multiple consecutive calls to hasNext() before calling next().

Hints

Hint 1

Because the underlying iterator can only be read forward once, maintain a single lookahead buffer storing the next element to return.

Hint 2

Use a flag or sentinel to track whether the lookahead buffer currently contains a valid element, and load the next valid element lazily when needed.

Hint 3

When advancing to the next target element, consume and discard exactly `step - 1` elements from the underlying iterator before capturing the next value.

Follow-up questions

  • How would you support a dynamic `setStep(new_step)` method that changes the step size on the fly between `next()` calls?
  • If the underlying sequence supports random access `__getitem__` (like an array or list), how would you optimize `next()` from `O(step)` to `O(1)`?
  • How would you ensure thread-safety if multiple threads might concurrently call `hasNext()` and `next()`?