Work down this list before reaching for a different library:
Stable identities. A string keyExtractor from real ids; renderItem, keyExtractor and separators defined at module level or in useCallback (extraData and Re-Rendering).
Cheap, memoized rows. Wrap the row in memo, pass primitives rather than fresh objects, and keep hooks, context reads and effects out of rows where you can.
Known sizes. Fixed-height rows plus getItemLayout skip measurement and make scrollToIndex exact.
Right-sized window. Lower windowSize to save memory on low-end phones, and raise initialNumToRender only to fill the first screen; removeClippedSubviews is already on by default on Android.
Small images. Thumbnails at display size, cached (expo-image 6,418 with recyclingKey, expo-image).
Measure in release. Debug builds run extra checks; profile a release build on a real, preferably old, phone with React Native DevTools 36,878 (React Native DevTools).
Then FlashList 7,238 . If blank rows still flash during flings, switch to FlashList (FlashList).