Prompt
The math behind getItemLayout and virtualization, for fixed-height rows:
makeItemLayout(rowHeight)→ returns(data, index) => ({ length, offset, index })visibleRange(scrollY, viewportHeight, rowHeight, itemCount)→{ first, last }— the inclusive indexes of rows intersecting the viewport, clamped to[0, itemCount - 1]
Solution
Mentor's take
This is the arithmetic that makes a 10,000-row list scroll at 60fps, and knowing it cold is what separates "I pass getItemLayout because the docs said so" from understanding virtualization.
Why each piece exists:
- Without
getItemLayout, FlatList must render every row above index N to know where N sits. Async measurement is whyscrollToIndexthrows without it and why fast scrolls show blanks. Providingoffset = rowHeight × indexturns layout into O(1) math — no rendering, no measurement, instant jumps. That's the real answer to "why does keyExtractor + getItemLayout matter": identity and geometry are the two things the virtualizer can't guess cheaply. visibleRangeis the virtualizer's core loop:floor(scrollY / rowHeight)finds the first intersecting row; the last one comes from the viewport's bottom edge —ceilthen-1because a row that touches the bottom edge pixel is still visible. Off-by-one here is exactly the class of bug that shows as a blank strip at the bottom of a fast scroll.- The clamps aren't decoration. Overscroll (iOS bounce) sends negative
scrollY; scroll-to-end sends a bottom edge past the content. Both would index outside the data. Handling the physical world's inputs — bounce, momentum — is the difference between the whiteboard version and the shipping version. - The empty list is its own case. With zero items there is no valid index, so clamping to
itemCount - 1(=-1) is nonsense. Return an empty range —{ first: 0, last: -1 }, wherelast < firstmeans "iterate nothing" — before the arithmetic runs. A list that renders and then empties (filter, search) hits this every time. - Real FlatList renders
windowSizeviewports around this range as a buffer; the math is identical with padding added to both ends.
Red flag: claiming getItemLayout speeds up rendering. It doesn't render anything faster — it removes measurement as a dependency for positioning, which unlocks scrollToIndex and eliminates layout passes. Precision about what it skips is the seniority signal.
Say it: "Fixed row height turns layout into arithmetic: offset is height times index, the visible window is floor and ceil over the scroll edges, clamped for bounce — that's all a virtualized list is doing."