Solving Scroll Jank in React Native with FlatList Tuning
Stop scroll jank in React Native. Learn how to configure FlatList's initialNumToRender and windowSize to balance memory usage and 60fps smoothness.
16 Mar 2026, 18:18 UTC

The Problem: Blank Spaces and Stuttering Lists
When rendering lists with thousands of items in React Native, you often encounter "jank"—visible frame drops or white gaps that appear during fast scrolling. This happens because FlatList uses virtualization, meaning it only renders a small subset of items to save memory. If the buffer of pre-rendered items is too small, the UI thread cannot mount new rows fast enough to keep up with the user's scroll speed.
The Strategy: Balancing Memory and Fluidity
To eliminate these gaps, you must tune how FlatList manages its render window. The goal is to provide enough pre-rendered content to cover fast scrolls without consuming so much RAM that the app triggers aggressive garbage collection, which ironically causes more stuttering.
Key Configuration Props
initialNumToRender: Determines how many items are rendered in the first batch. Setting this too high slows down the initial screen mount; setting it too low can cause a flash of empty content on startup.windowSize: This controls the maximum number of items rendered outside of the visible area. It is measured in "viewport heights." A value of 21 (the default) means 10 viewports above, 1 visible viewport, and 10 viewports below. This is often overkill for simple lists and can waste memory.
Worked Example: Optimizing a 10,000-Item List
In a scenario with a large dataset of simple text rows, default settings often lead to frame times exceeding 40ms on mid-tier Android devices. By tightening the window and specifying the initial batch, we can stabilize the frame rate.
import React from 'react';
import { FlatList, Text, StyleSheet, View } from 'react-native';
// Generating a large dataset for demonstration
const DATA = Array.from({ length: 10000 }, (_, i) => ({ id: i.toString(), title: `Item ${i}` }));
const OptimizedList = () => {
return (
item.id}
renderItem={({ item }) => (
{item.title}
)}
// Render 10 items immediately to fill the screen quickly
initialNumToRender={10}
// Render 7 viewports above and 3 below the current view
windowSize={{ viewportStart: 7, viewportStop: 3 }}
/>
);
};
const styles = StyleSheet.create({
item: { padding: 20, borderBottomWidth: 1, borderColor: '#ccc' },
});
export default OptimizedList;
In this configuration, windowSize={{ viewportStart: 7, viewportStop: 3 }} prioritizes the content the user has already passed (useful for scrolling back up) while keeping a lean buffer ahead of the scroll direction to maintain 60fps performance.
Trade-offs and Memory Constraints
Tuning is a sliding scale. If you increase windowSize to very high values (e.g., 40+), you will virtually eliminate blank spots, but memory consumption will spike. On low-end devices, this leads to frequent garbage-collection pauses, which create the same stuttering effect you were trying to solve.
How to verify the result:
- Run the app in development mode on a physical device.
- Open the Developer Menu (
⌘+Don iOS orCtrl+Mon Android) and select Show Perf Monitor. - Observe the UI thread frame rate. A healthy list should stay near 60fps (approx 16.6ms per frame).
- If you see frames jumping to 40ms or higher during scrolls, incrementally increase your
windowSize.
Actionable Closing
Don't rely on default FlatList props for lists exceeding 100 items. Start by setting initialNumToRender to the number of items that physically fit on your screen, then use the Perf Monitor to find the smallest windowSize that prevents blank spots. Always test on your lowest-spec supported device to ensure memory usage remains stable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.