I have shipped a few React Native apps now. Every single time, at some point during development, the app started to feel slow. Scrolling got janky. Transitions stuttered. The UI thread just could not keep up. And every single time, the root cause came down to a small set of avoidable mistakes.
This is not a comprehensive guide to React Native internals. It is a practical rundown of the patterns that actually made a difference when I was debugging performance in real apps.
Stop Re-rendering Everything
The most common performance problem I see in React Native is unnecessary re-renders. A parent component updates its state, and suddenly dozens of child components re-render even though nothing relevant to them changed.
The fix is usually straightforward. Wrap components that are expensive to render with React.memo. Move state as close as possible to where it is used. Use useCallback for functions passed as props to memoized children, and useMemo for expensive computed values.
One thing to watch out for: inline object and array literals in JSX. Writing style={{ flex: 1 }} creates a new object on every render. Move styles into StyleSheet.create calls outside your component, or at least into a variable defined outside the render path.
FlatList is Your Friend, if You Use It Right
If you are rendering a long list and it is slow, the first question is whether you are even using FlatList. Mapping over an array inside a ScrollView renders every item at once. That is fine for five items. It is a disaster for two hundred.
But FlatList alone is not enough. You need to help it. Set removeClippedSubviews to true. Tune initialNumToRender, maxToRenderPerBatch, and windowSize based on your item height and list length. If your items are a fixed height, pass getItemLayout so FlatList can skip measurement entirely.
Also, make sure your renderItem is memoized. If the function reference changes on every render, FlatList will re-render every visible item even when the data has not changed.
Images Will Kill Your Frame Rate
Images are one of the most overlooked sources of jank. Decoding a large JPEG on the main thread mid-scroll is enough to drop frames noticeably.
A few things that help: resize images server-side before serving them (do not ship a 4K image to a phone screen that renders it at 100px). Use a caching library like react-native-fast-image instead of the built-in Image component, it handles caching and priority queuing far better. If you have a lot of images in a list, set the priority prop so off-screen images do not compete with visible ones.
Move Work Off the JS Thread
React Native runs your JavaScript on a single thread. If you are doing heavy computation there, you are competing with rendering, event handling, and animations all at once.
For animations, stop using the Animated API with useNativeDriver: false. Almost every animation can run on the native thread with useNativeDriver: true. If your animation requirements go beyond what Animated supports, look at Reanimated 3. It lets you write animation logic in worklets that run directly on the UI thread, with zero JS overhead.
For CPU-intensive work like parsing, sorting large datasets, or processing images, consider moving it to a background thread using a library like react-native-threads or by calling native code via a JSI module.
Profile Before You Optimise
The worst thing you can do is guess. I spent an afternoon once memoizing components that were not the bottleneck, while a single expensive list item was causing every scroll event to take 40ms.
Use the React DevTools Profiler to see which components are re-rendering and how long they take. Use Flipper with the Performance plugin to see JS thread and UI thread frame times side by side. On Android, enable the on-device FPS overlay in developer settings.
Once you have data, fix the biggest problems first. A 50ms render in a component called once at startup matters much less than a 5ms render in a list item that runs sixty times per scroll.
A Few Small Things That Add Up
Beyond the big patterns, there are a handful of small habits that consistently help. Use Hermes as your JS engine, it is faster at startup and uses less memory than JSC. Enable the New Architecture if your dependencies support it; the new renderer and concurrent features make a real difference. Keep your bundle size down by auditing dependencies with react-native-bundle-visualizer and removing anything you are not actually using.
None of this is magic. Performance comes from understanding where time is actually being spent and fixing the worst offenders one by one. The patterns above are just the places I almost always find the problems.



