Fast on a cheap phone
Most Mangrove riders carry a 2 to 3 GB Android phone, and an app that feels fine on your laptop's emulator can stutter, show blank rows and get closed by the system on theirs. This lesson shows you how to measure where the time and memory go, then fix lists, images, memory and startup in that order of evidence.
By the end you can
- Explain the 16.7 ms frame budget and tell JS thread jank from UI thread jank
- Measure a slow screen in a release build on a cheap Android before you change any code
- Tune a
FlatListfor fixed-height rows and know when FlashList is worth the switch - Show right-sized, cached images with expo-image and stop listener leaks
- Cut cold start time with Hermes bytecode, a lighter first screen and lazy tabs
The everyday picture
Picture a small ferry that leaves the jetty every 17 seconds, exactly on time, full or not. Two people work the jetty. The cook in the kitchen behind prepares the meals that must go out. The loader at the water carries whatever is ready onto the boat. If the cook is slow, the ferry leaves with last trip's sign in the window and no new food, and the people across the river wait for the next one.
Your app works the same way. The screen refreshes on a fixed beat, 60 times a second on most phones. The JavaScript thread is the cook: it runs your React code and decides what should change. The UI thread is the loader: it draws what is ready. A cheap phone is a smaller kitchen with a slower cook, so the same recipe that fits on a flagship misses the ferry. You cannot make the ferry wait. You can only give the cook less to do per trip, and keep heavy lifting off his hands.
The frame budget and the two threads
At 60 Hz the screen shows a new picture every 1000 / 60 = 16.7 milliseconds. That is the frame budget. If the work for a frame is not done in time, the phone shows the old picture again. That is a dropped frame, and a run of dropped frames is jank. A phone with a 90 Hz screen gives you 11.1 ms. To count them, divide: a frame that takes 45 ms at 60 Hz spans three refresh slots, so two pictures were missed.
In a React Native app two threads matter most. The JS thread runs your components, effects, event handlers and any JavaScript-driven animation. The UI thread (the main thread) draws native views, handles touches and runs native scrolling. With the New Architecture, the default since React Native 0.76 (How React Native works), JavaScript can call native code directly through JSI, but your React renders still run on the JS thread. So the two kinds of jank have different symptoms:
- JS thread busy: a tap on "Accept" seems to do nothing for half a second, a JavaScript-driven animation freezes, and while you scroll a long list you see blank rows, because new rows are rendered by JavaScript and it cannot keep up.
- UI thread busy: the scroll itself stutters. Common causes are too many or too deeply nested views, very large images being decoded, and heavy shadows or transparency drawn over each other.
Scrolling a ScrollView lives on the UI thread, so it keeps moving even when JavaScript is stuck. Animations are where this matters most. An animation that updates a value from JavaScript on every frame needs the JS thread 60 times a second, and freezes the moment a render takes 50 ms. Run animations on the UI thread instead. React Native's Animated does that for transform and opacity when you set useNativeDriver: true. Reanimated goes further: its animation code runs as small functions on the UI thread. Reanimated 4 works only on the New Architecture and needs the react-native-worklets package next to it.
Quick check
While a rider scrolls the job list fast, the scroll is smooth but the bottom of the list shows empty space for a moment before rows appear. Which thread is behind?
Smooth scrolling means the UI thread is fine; it moves the content it already has. The blank space is rows that JavaScript has not rendered yet. The data is already on the phone, so the network is not the cause. You fix this by giving each row less JavaScript work, or by rendering ahead a little more.