Design a Recoverable Iterator
xAISoftware Engineer · Technical ScreenLast updated
Design a forward-only iterator wrapper that supports saving and restoring cursor state via checkpoints, testing buffering strategies and memory management.
Design a Recoverable Iterator
In machine learning data pipelines and speculative execution engines, data streams often need to support backtracking—for instance, when evaluating candidate token sequences or rolling back failed training micro-batches.
Design and implement a RecoverableIterator class that wraps a standard forward-only iterator of integers and provides checkpointing and restoration capabilities.
Class Interface
from typing import Iterator
class RecoverableIterator:
def __init__(self, iterator: Iterator[int]):
"""Initializes the wrapper around a forward-only iterator."""
pass
def has_next() -> bool:
"""Returns True if there are more elements available from the current position."""
pass
def next() -> int:
"""Returns the next integer and advances the pointer.
Raises StopIteration if no more elements are available."""
pass
def checkpoint() -> int:
"""Saves the current cursor position and returns a unique checkpoint_id."""
pass
def restore(checkpoint_id: int) -> None:
"""Restores the iterator's cursor to the position captured by checkpoint_id.
Raises ValueError if the checkpoint_id is invalid."""
pass
Requirements & Behavior
- The input
iteratoris a single-pass stream; you cannot rewind or index it directly. - Calling
checkpoint()records the exact position such that the subsequent call tonext()will return the same element that would have been returned right aftercheckpoint()was called. - Calling
restore(checkpoint_id)moves the read cursor back to that saved state. A checkpoint can be restored multiple times. - Checkpoints remain valid even after the iterator reaches the end of the stream.
Example
stream = iter([10, 20, 30, 40, 50])
rit = RecoverableIterator(stream)
rit.next() # Returns 10
cp1 = rit.checkpoint() # Checkpoint captured at position of element 20
rit.next() # Returns 20
rit.next() # Returns 30
cp2 = rit.checkpoint() # Checkpoint captured at position of element 40
rit.next() # Returns 40
rit.restore(cp1) # Rewind to cp1
rit.next() # Returns 20
rit.next() # Returns 30
rit.restore(cp2) # Jump forward to cp2
rit.next() # Returns 40
rit.next() # Returns 50
rit.has_next() # Returns False
Constraints
- Up to
10^5total calls acrossnext(),has_next(),checkpoint(), andrestore(). - Each method must run in
O(1)amortized time. - The total number of elements yielded by the underlying iterator is at most
10^5.
Edge Cases
- Creating a checkpoint at the very beginning (index 0) or after exhausting the iterator.
- Calling
has_next()multiple times consecutively without callingnext(). - Calling
restore()to move backward as well as forward across checkpoints. - Passing an empty underlying iterator.
Hints
Hint 1
Because the input iterator can only be advanced forward, you need an internal dynamic array (buffer) to store elements once they are fetched from the underlying iterator.
Hint 2
Maintain an integer index representing the current read pointer in your internal buffer, advancing into the underlying iterator only when the read pointer exceeds the buffered elements.
Hint 3
A checkpoint can simply map a unique identifier to the integer index of the current read pointer at the moment `checkpoint()` was invoked.
Follow-up questions
- If the underlying stream is infinite or massive, an unbounded internal buffer will cause memory issues. How would you support a `release(checkpoint_id)` method to garbage-collect unneeded buffer prefixes?
- How would you adapt this class to be thread-safe if multiple worker threads might read or restore concurrently?
- Could you implement this with transactional semantics (e.g., nested `begin_checkpoint()`, `commit()`, and `rollback()`) using a stack?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.