FlatList Scroll Jank? Fix It with getItemLayout for Fixed-Height Rows
FlatList jank on low-end devices often comes from measuring every row. Defining getItemLayout with fixed heights skips layout passes entirely — here's how to implement it, verify the gain, and avoid the variable-height trap.
08 Oct 2025, 22:11 UTC

The Problem: Why Fast Scrolling Stutters
You're scrolling through a long list in your React Native app. On a flagship phone it's buttery smooth. On a budget Android device or an older iPhone, frames drop, the UI janks, and the experience feels cheap. The culprit is often FlatList doing more work than necessary.
By default, FlatList measures every row during layout passes to calculate positions. For a list of 500 items, that's 500 measure-layout cycles before the first frame even paints. On low-end devices, this measurement overhead translates directly into missed frames during fast scrolls.
How getItemLayout Changes the Math
The getItemLayout prop tells FlatList the exact position and size of every row up front. Instead of measuring, the list computes offsets arithmetically: offset = index * itemHeight. This eliminates the measurement pass entirely.
The prop expects a function returning an object with three keys for a given index:
length— the fixed height of each row (in density-independent pixels)offset— the distance from the list start to this row's top edgeindex— the row identifier (usually the same as the input index)
When every row shares the same height, this function is a one-liner. The performance gain comes from skipping measure and onLayout callbacks for invisible rows.
Worked Example: A 60dp Row List
Assume a chat message list where every row is exactly 60 dp tall. Here's the minimal implementation:
const ITEM_HEIGHT = 60;
const getItemLayout = (data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index
});
<FlatList
data={messages}
renderItem={({ item }) => <MessageRow message={item} />}
getItemLayout={getItemLayout}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
/>Run this on a device with react-native-performance-monitor enabled (shake device → Show Perf Monitor). Compare frame times before and after adding getItemLayout. On a Pixel 3a-class device, you'll typically see the JS thread frame time drop from 16–20 ms to 8–12 ms during rapid fling scrolls.
Pair it with initialNumToRender and maxToRenderPerBatch to limit the initial mount work. windowSize={5} keeps only five screen-heights of content mounted, reducing memory pressure.
Where It Breaks: Variable Heights and Dynamic Content
The optimization assumes deterministic heights. If your rows wrap text, expand on tap, or load images that change dimensions, getItemLayout will report wrong offsets. The result: scroll position jumps, blank gaps, or rows overlapping.
Common failure modes:
- Text that wraps differently per device width — height becomes unpredictable
- Images loading asynchronously — initial height is zero, then expands
- Accordion rows that toggle open/closed
If you must support variable heights, consider CellMeasurer from react-virtualized (web) or accept the measurement cost. For React Native, the pragmatic choice is often: design for fixed heights, or don't use getItemLayout.
Verifying the Gain Without Guessing
Don't rely on feel. Use these concrete checks:
- Performance Monitor (in-app): Enable
Show Perf Monitorin the dev menu. Watch the JS thread FPS during a fast fling. Target: sustained 60 FPS (16.6 ms/frame) on mid-tier devices. - Android Studio Profiler / Xcode Instruments: Record a scroll session. Filter for
UIManagerorYogalayout passes. WithgetItemLayout, you should see near-zero layout passes for offscreen rows. - Visual stress test: Scroll from top to bottom in under 2 seconds. Look for flashing, jumping, or misaligned rows. If any appear, your height assumption is wrong.
If the list passes all three, the optimization is safe for production.
When to Apply This
Use getItemLayout when:
- Rows are genuinely uniform (avatar + single-line text, fixed-height cards, media thumbnails)
- You target low-end Android or older iOS devices
- List length exceeds ~100 items
Skip it when:
- Content height depends on dynamic text, images, or user interaction
- You're building a prototype and the list is short
- The team lacks capacity to maintain the height contract across design changes
Start by measuring your current scroll performance. Add getItemLayout to one list, verify with the profiler, then roll out selectively. The 30–50% latency reduction on budget hardware is real — but only when the height promise holds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.