The Page Turner
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Foursquare
Problem
A paged API returns pages, a list of pages where each page is a list of items and any page can come back empty. Inside paginate(pages), build a class that implements __iter__ and __next__ and hands out the items one at a time, keeping its place between calls, then return everything it yields as a list.
Nobody loads everything at once.
Example 1
Input:
pages = [[1,2],[3,4]]
Output:
[1,2,3,4]
Example 2
Input:
pages = []
Output:
[]
Worked solution and explanation
Why this problem exists in real interviews
Pagination shows up in every API and every analytics pipeline. Interviewers use this prompt to check whether you understand the Python iterator protocol (__iter__ and __next__), and whether you can flatten a nested structure lazily without materializing the whole feed in memory.
Break down the requirements
Step 1: Make the class its own iterator
__iter__ returns self so for item in Paginator(pages) and list(Paginator(pages)) both work. The driver paginate(pages) calls list(Paginator(pages)), which calls __iter__ then __next__ until StopIteration.
Step 2: Track two indices, not one flat cursor
Keep page_idx for which page you are on and item_idx for the position inside that page. A single flat cursor would force you to either pre-flatten the input or recompute offsets on every call.
Step 3: Skip empty pages and stop cleanly
Use a while loop in __next__ so that an empty page advances page_idx without returning anything. When page_idx runs past the end, raise StopIteration (do not return None). Expose a tiny top-level driver paginate(pages) that returns list(Paginator(pages)) so the harness has a single callable to invoke.
The solution
def paginate(pages: list[list]) -> list:
class Paginator:
def __init__(self, pages: list[list]):
self.pages = pages
self.page_idx = 0
self.item_idx = 0
def __iter__(self):
return self
def __next__(self):
while self.page_idx < len(self.pages):
page = self.pages[self.page_idx]
if self.item_idx < len(page):
item = page[self.item_idx]
self.item_idx += 1
return item
self.page_idx += 1
self.item_idx = 0
raise StopIteration
return list(Paginator(pages))Common follow-up questions
- Rewrite `Paginator` as a generator function. Which version is shorter, and why might production code still prefer the class form? (Tests whether the candidate knows generators implement the iterator protocol for free, and that classes win when you need to expose extra state (current page, items seen) for debugging or resumption.)
- How would you support `peek()` to look at the next item without consuming it? (Tests buffering. The clean answer is to store one lookahead item and have `__next__` return the buffer first. Watch whether they handle the case where the buffer is empty after exhaustion.)
- What changes if `pages` is itself a generator of pages rather than a list of lists? (Tests understanding of `len()` versus iteration. They cannot index `self.pages[self.page_idx]`; they must call `next()` on the outer generator and catch `StopIteration` to terminate.)