React Native Performance: The Patterns That Make It Production Ready
React Native performance optimization involves identifying and eliminating the specific causes of frame drops, slow navigation, and janky animations in React Native apps. The main performance categories are JavaScript thread work (heavy computation, excessive re-renders), main thread contention (synchronous native calls, layout-triggering operations), network and data loading patterns (waterfall requests, missing loading states), and bundle size (large JavaScript payloads that increase startup time and parse time).
Written by Yashveer Singh, founder of Yashveer Labs.
What you need to know
- Excessive re-renders are the most common source of React Native performance problems. Use React DevTools Profiler to identify components that re-render unnecessarily.
- Animations must run on the native thread to be frame-perfect. Use Reanimated 2+ for any animation that needs to be smooth regardless of JavaScript thread load.
- Virtualize long lists. FlatList with windowSize and removeClippedSubviews is not optional for lists with more than 50 items.
- Image optimization is disproportionately impactful. Memory pressure from large images causes the OS to kill background processes and slows the entire app.
- Measure before optimizing. React Native has good profiling tooling (Hermes Profiler, React DevTools). Fix the measured bottleneck, not the suspected one.
The core argument
React Native apps that feel slow rarely have architectural problems. They have accumulated a set of specific, fixable patterns: components that re-render on every keystroke because a function reference is recreated inline, lists that render 500 items simultaneously instead of the 20 that are visible, images loaded at 4x display resolution because the API returns originals, and animations that run on the JavaScript thread and drop frames when network requests are in flight.
The diagnostic process matters more than the fix library. I have seen teams add memoization everywhere after reading a blog post about re-renders, without measuring which re-renders were actually expensive. Memoization has a cost: the useMemo and useCallback overhead is real, and memoizing components that re-render cheaply adds overhead without benefit. The correct approach is: measure which components render most frequently and most expensively using React DevTools Profiler, then apply memoization to those specific components with concrete measurements of improvement.
The patterns that consistently move performance metrics in my experience across React Native projects: moving navigation transition animations to Reanimated (eliminates frame drops during navigation), adding getItemLayout to FlatList for fixed-height items (eliminates the layout calculation overhead on scroll), and deferring data fetching with InteractionManager.runAfterInteractions until navigation transitions complete. These three changes together have produced the most consistent improvement across different React Native projects.
Common mistakes
- Rendering all items in a list without virtualization. A ScrollView with 200 items renders all 200 components simultaneously, regardless of how many are visible. FlatList virtualizes rendering, keeping only visible items (plus a configurable buffer) in memory. Any list with more than 30 to 50 items should use FlatList, not ScrollView with mapped items.
- Creating new object and function references on every render.
onPress={() => handlePress(item.id)}creates a new function on every render.style={{ marginTop: 10 }}creates a new object on every render. Both defeat React.memo and cause unnecessary child re-renders. Use useCallback for event handlers passed to components and StyleSheet.create for styles.
- Running synchronous storage reads on the main thread. AsyncStorage and SecureStore operations are asynchronous for a reason. Synchronous storage reads block the JavaScript thread. Initialize state from storage in a useEffect and show a loading state during initialization rather than blocking render on storage reads.
- Not using Hermes as the JavaScript engine. Hermes is the optimized JavaScript engine for React Native, with faster startup time and lower memory usage than V8. New React Native apps use Hermes by default. Older projects that have not migrated to Hermes are leaving significant startup performance on the table, particularly on Android.
- Debugging performance in the simulator instead of on a real device. The iOS simulator and Android emulator run on the Mac's CPU and do not reflect the memory constraints, CPU performance, or GPU limitations of physical devices. Performance that looks acceptable in the simulator may be unacceptable on a mid-range Android device. Always validate performance on physical hardware, and specifically on a mid-range Android device if Android is in the target market.
Where to start
- Profile with React DevTools Profiler to find re-render hotspots. Enable the profiler, perform the interaction that feels slow, and review the flame chart for components that rendered multiple times or took more than a few milliseconds. These are the optimization targets. Apply React.memo, useMemo, or useCallback to the specific components with measurable re-render cost.
- Audit image loading. Check the images displayed in the app and confirm they are sized appropriately for the display dimensions. Install expo-image or react-native-fast-image if not already in use. Verify that FlatList-rendered images are properly dequeued when scrolled out of view.
- Check FlatList configuration for long lists. For fixed-height list items, add getItemLayout to eliminate per-item height calculation. Set windowSize to 5 to 7 (items to render above and below the viewport). Add removeClippedSubviews={true} to unmount items outside the viewport on Android. These three FlatList props together have a significant impact on scroll performance for long lists.
Related reading
Frequently asked
The work I take and why
I take work that compounds. I do not take work that is rework with extra steps. Yashveer Singh, founder of Yashveer Labs. If the topic on this page is what you are dealing with, the question is not whether it can be solved. It can. The question is whether you want to solve it once or four times. I am the person who solves it once.
Posts that line up with this one.
- Cross Platform and Mobile Development
iOS TestFlight vs Internal Testing: A Comparison
TestFlight and Apple's internal testing tools serve different purposes at different stages of mobile development. Here is when to use each, what the review implications are, and how to run a clean beta program.
- Cross Platform and Mobile Development
Kotlin Multiplatform vs Flutter vs React Native: A Real Comparison
Three serious cross-platform options for mobile in 2026. Here is how to choose between them without guessing.
- Cross Platform and Mobile Development
Mobile App Rewrites: When They Are Inevitable and When They Are a Mistake
A mobile app rewrite feels like a fresh start. Often it is a six-month detour that reproduces the same problems in a new codebase. Here is how to decide whether you actually need a rewrite or whether targeted refactoring will solve the problem.
- Cross Platform and Mobile Development
Mobile Authentication: Biometrics, Magic Links, and the Death of Passwords
Passwords on mobile are a friction problem and a security problem. Here is how biometrics, magic links, and passkeys are replacing them, and what to implement for a mobile app that needs both security and low friction.