Web & Mobile ¡ 5 minute read
React Native Performance: Where the Time Actually Goes
React Native performance work concentrates in render scope, list configuration, and startup. The largest wins usually come from rendering fewer components per state change, configuring lists properly, and moving initialisation off the path to first paint rather than from micro-optimising individual functions.
React Native performance work usually starts in the wrong place, because the obvious suspects are rarely the expensive ones. This guide covers where the time actually goes, drawing on FISTA Solutions' web and mobile work.
What are the usual causes?
Five patterns account for most of what teams find.
| Cause | Symptom |
|---|---|
| Excess re-rendering | Stutter on any state change |
| Unconfigured long lists | Scroll jank, memory growth |
| JavaScript-driven animation | Dropped frames during motion |
| Full-resolution images | Memory pressure |
| Blocking startup work | Slow cold start |
| Heavy synchronous work | Interface freezes |
How do you reduce re-rendering?
By keeping prop identity stable and by splitting components so state changes touch less.
A new object or inline function passed as a prop makes a memoised child re-render anyway. Memoising values and callbacks, and keeping state in the smallest component that needs it, is the core discipline.
Use the profiler to find components rendering most often and why. The answer is usually one context or one state hook sitting too high in the tree.
What does list configuration involve?
Stable keys, known item layout, and windowing tuned to the content.
Supplying item layout where heights are fixed removes measurement work and makes scrolling to an index instant. It is the single most effective list change when it applies.
Window size and batch settings default conservatively. For simple rows you can render more per batch; for complex rows, fewer. Both directions help, and the right value depends on item cost.
How much does the bridge cost?
Chattiness costs; payload size mostly does not.
Many small calls per frame serialise and queue, which is what produces stutter during interaction. One larger call is generally cheaper than fifty small ones.
Animations are the classic case. Driving animation from JavaScript means a message per frame; using the native driver moves it off the JavaScript thread entirely and the difference is immediately visible.
How should images be handled?
Sized explicitly, resized server-side where possible, and cached.
A large source image displayed small still decodes at full resolution unless resized. Serving appropriately sized images from the server is the cleanest fix and helps bandwidth too.
Give every image explicit dimensions so layout does not shift when it loads, and use a caching image component for network images in lists. See web performance optimization guide.
What slows startup?
Work executed before the first screen renders.
Module-level initialisation runs at import, so a dependency doing setup work at import time delays every launch. Synchronous storage reads and blocking network calls on the first screen do the same.
Audit the path to first paint and defer everything else. Lazy-load screens and features the first screen does not need, and measure cold start on a low-end device rather than a simulator.
When do you need native modules?
When the work is genuinely expensive and belongs off the JavaScript thread.
Image processing, cryptography, large data transformations, and continuous sensor handling are all better native. Writing one is a real cost, so reserve it for work that has been measured as a bottleneck.
Most applications never need a custom module. Reaching for one before profiling is the usual mistake. See cross platform vs native decision guide.
What are the common mistakes?
Profiling development builds. Testing only on flagships. Inline objects and functions as props. Default list configuration on long lists. JavaScript-driven animation. And writing native modules before measuring.
How do you test it?
Measure cold start, scroll frame rate on the longest list, and time to interactive on the heaviest screen, all on the cheapest supported device.
Re-measure on each release. Startup regressions arrive quietly with new dependencies.
What does it cost to operate?
Mostly engineering time, concentrated in the first few days. The common causes are well understood and fixing them is not research.
A set of low-end test devices is a small one-off cost that pays for itself repeatedly.
What should you measure?
Frame rate during scroll and animation, render counts per interaction, cold start time, time to interactive per screen, and peak memory on image-heavy screens.
What about AI features?
Keep model calls off the JavaScript thread's critical path and stream output rather than blocking on a full response.
Throttle interface updates during streaming. Re-rendering text on every token is expensive and, past a certain rate, looks worse than updating a few times a second. See streaming UI patterns for AI apps.
When is this the wrong approach?
An app with a handful of simple screens and no long lists rarely has a performance problem worth chasing. Measure before optimising; the discipline is finding the bottleneck, not applying every technique available.
What should you do first?
Build in release mode, run on the cheapest device you support, and scroll your longest list. That one measurement usually identifies the work worth doing.
How FISTA Solutions helps
FISTA Solutions builds and operates production systems through web and mobile, AI enablement, and staff augmentation: profiling done on release builds and low-end hardware, render scope narrowed before native modules are ever considered, decisions documented with their reasoning, and handover that leaves your team able to maintain what was delivered. The record is 150+ projects for 50+ companies across 12+ countries.
To scope this work, message FISTA on WhatsApp, or read flutter performance guide.
Share-ready article cover
Download the generated social format.
Clear answers
Questions raised by this field note.
Straightforward guidance for evaluating scope, fit, and the next step.
01Why measure in release builds?
Because development builds run without optimisation and with extra instrumentation, so they are far slower and slower in different places. Conclusions drawn from a development build routinely point at the wrong code.
02What causes most jank?
Components re-rendering when nothing they display has changed, usually because of new object or function identities passed as props. Narrowing render scope is the highest-return change in most applications.
03How should long lists be configured?
With stable keys, item layout supplied where heights are known, and window size tuned to the content. Default configuration is conservative and frequently the cause of scroll stutter on long lists.
04Does bridge traffic still matter?
Chattiness does. Many small calls per frame cost more than one large payload, and animations driven from JavaScript per frame are the classic example of this failure.
05What makes startup slow?
Everything executed before the first screen renders â module initialisation, synchronous storage reads, and blocking network calls. Deferring anything not required for first paint is usually the largest single win.
Continue exploring
Related capabilities
Start with the hard problem
Need the outcome owned, not merely analyzed?
Tell us where delivery is constrained. Weâll map the fastest credible path from intent to verified production.