SyncTrix logoSyncTrix
All articles
Experience10 min read

Debugging React Native performance: lists, re-renders and the JS thread

Nearly all React Native jank comes from three causes. How to identify which one you have before you start optimising.

By Aarav Patel
Debugging React Native performance: lists, re-renders and the JS thread

React Native performance problems look alike from the outside - scrolling stutters, taps feel delayed, screens take a moment to appear - and have distinct causes with distinct fixes. Guessing wastes time, because the most common intuition, that the framework is slow, is almost never the explanation.

01Identify which thread is blocked

Work happens on the JavaScript thread and the UI thread, and which one is saturated tells you what class of problem you have.

If the JS thread is blocked, animations driven by JavaScript stutter but native gestures still respond. If the UI thread is blocked, everything freezes including native scroll. That distinction narrows the search immediately.

JS thread saturation is usually expensive work in render, oversized state updates, or synchronous processing of a large payload. UI thread saturation is usually image decoding, shadows, or too many views.

02The three causes worth checking first

In practice these account for the overwhelming majority of complaints.

  • Unvirtualised or badly configured lists. Rendering a long list without recycling, or with a changing key, rebuilds far more than necessary. Stable keys and a properly sized window fix most scroll jank.
  • Re-renders from context or state scoped too widely. A context holding frequently changing values re-renders every consumer; splitting it by update frequency is often a one-line fix with a large effect.
  • Animations on the JS thread. Anything driven by state updates per frame will stutter under load; the native driver exists precisely so animation survives a busy JS thread.

03Images are the quiet cost

Image handling causes more real-world jank than component structure. Decoding a large image is expensive and happens on the UI thread; a list of full-resolution images will stutter regardless of how well the list is written.

Serve images sized for their display size rather than resizing on device. A 4000-pixel image rendered into a 100-point thumbnail costs full decode for no visual benefit, and in a list it costs it repeatedly.

Cache decoded images, not just downloaded bytes. Re-decoding on every scroll pass is a common and invisible cost.

04When it really is architecture

If you have fixed lists, re-renders and images and the app is still slow, the remaining causes are usually structural: too much state held too high, a data layer that refetches on every focus, or heavy computation that belongs off the main thread.

At that point the fix is not an optimisation but a change in where work happens. Moving parsing and transformation off the JS thread, or memoising derived data properly, resolves what component-level tuning cannot.

Topics

react native performancereact native jankflatlist performancereact native profilingmobile ui performance

Aarav Patel

Principal Engineer · SyncTrix

Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.

Building something like this?

SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.

Talk to an engineer