Iterators
What is this?
An iterator is a little device that hands you the next item in a sequence each time you ask, without ever laying out the whole sequence at once. Picture a Pez dispenser: it gives you one candy per click and keeps the rest tucked away. The design trick is to hold just enough state โ a pointer or two, a small stack โ so you can always produce the next item on demand and know when you've run out.
๐ก Fun fact: This lazy, one-at-a-time style is how huge data pipelines stay fast โ tools like Python generators and database cursors stream billions of rows without ever loading them all into memory, producing each row only at the moment you ask for it.
๐ The 3 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
Core idea: An iterator exposes a sequence one element at a time via
next()/hasNext()โ without ever building the whole sequence in memory. The design challenge is holding just enough state (a stack, a pair of pointers, a run index) to produce the next element on demand, and maintaining one clear invariant between calls.
Lazy beats eager
The naive move is to flatten everything up front in the constructor, then index it โ but that defeats the purpose: O(total) memory, and pointless if the caller stops early. A real iterator keeps a small cursor and computes the next element only when asked:
The recurring trick is an invariant maintained by a helper: after construction and after every next(), the cursor sits on a valid element (or past the end). One _advance()/_push_left() routine enforces it; everything else falls out.
The three problems
| Problem | State held | Invariant |
|---|---|---|
| Binary Search Tree Iterator | a stack of un-yielded ancestors | the stack top is the next-smallest; O(h) space, amortized O(1) next |
| RLE Iterator | index into the runs + remaining count | consume from runs lazily; never expand the decoded sequence |
| Flatten 2D Vector | (outer, inner) pointers + _advance() |
the cursor always rests on a real element or off the end (skip empty inner lists) |
Each one is a different shape of "produce the next element with bounded state": a tree (defer the right subtree via a stack), compressed data (walk runs without decompressing), and a jagged collection (two pointers that skip gaps).
The pattern
Decide the minimal state that lets you produce the next element, then write one helper that re-establishes the "cursor is valid" invariant after each call.
hasNext()is "is the cursor valid?";next()returns the current element and advances. Each element is touched O(1) amortized โ the deferred work (pushing a left spine, skipping empties) averages out.
๐ Draw it yourself
- The breathing stack. For the BST iterator, draw the stack holding the leftmost-spine; pop on
next(), then push the left spine of the popped node's right child. - Skip the empties. For Flatten 2D Vector, draw rows with some empty inner lists and an
_advance()that hops the cursor over them to the next real element.
Snap photos and embed them with the /host-diagrams skill.
Key takeaways
- Lazy, not eager: hold a small cursor; never pre-materialize the whole sequence.
- One invariant, one helper: "the cursor is on a valid next element," re-established by
_advance()/stack-push after each call. - Amortized O(1): deferred work (left-spine pushes, empty-list skips) averages to constant per
next(); space is O(state), not O(n). - Same idea, three shapes: tree (stack), compressed runs (run index), jagged 2-D (two pointers).
- Why interviewers like it: iterators test whether you reason about state between calls and laziness โ the core of streaming and cursor APIs.
Order: Binary Search Tree Iterator โ RLE Iterator โ Flatten 2D Vector.
RLE Iterator
Core idea: The input is already compressed as
(count, value)runs โ so to consumenitems you subtract from the current run's count and skip whole runs you exhaust, never expanding the runs into the giant sequence they describe.
Problem, rephrased
You're streaming a paint-by-numbers instruction tape for a plotter. Instead of listing every cell's color, the tape is compressed as alternating (count, color) pairs:
Read it as "three 8s, zero 9s, two 5s", which describes the decoded sequence:
8 8 8 5 5
You build the iterator once with that tape, then call next(n):
next(n)consumes the nextncells of the decoded sequence and returns the color of the last cell consumed.- If fewer than
ncells remain, it consumes whatever is left and returns-1(we ran off the end). - Consecutive calls continue where the last one stopped โ the cursor is sticky.
Operations / expected
Starting from tape = [3, 8, 0, 9, 2, 5] โ decodes to [8, 8, 8, 5, 5]:
| Call | Cells consumed (this call) | Remaining after | Returns | Why |
|---|---|---|---|---|
next(2) |
8 8 |
8 5 5 |
8 | last of the two consumed is an 8 |
next(1) |
8 |
5 5 |
8 | finishes off the run of 8s |
next(1) |
5 |
5 |
5 | crossed into the run of 5s |
next(2) |
5 then nothing |
โ (empty) |
-1 | only 1 left, needed 2 โ exhausted |
The headline catch: a single count can be up to a billion. You must answer all of this without ever materializing [8, 8, 8, 5, 5, โฆ].
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson