Lesson 5 of 7 · 35 min

React Native Performance on Low-End Android Devices

Measure and fix slow startup, janky lists and heavy renders on the budget phones many users actually own.

In this lesson1. Test on the phones your users own2. Lists and rendering3. Startup time and bundle size4. Animations, memory and the JavaScript thread

Test on the phones your users own

Many developers build on flagship phones and fast laptops, while a large share of users, particularly in India, Southeast Asia and Africa, use budget Android devices with limited memory and slower processors. An app that feels smooth on a new iPhone can stutter badly on an entry-level Android phone. Keep at least one low-end Android device in your team and use it for every feature review. Emulators on a powerful laptop do not reproduce real device limits.

Always measure performance in a release build. Development builds include extra checks and debugging tools that make everything slower, so judging performance in development mode leads to wrong conclusions. Use the React Native performance documentation, Android Studio profiler, the React DevTools profiler and tracing tools to find where time is actually spent. Fix the slowest measured problem first rather than guessing.

Set simple performance budgets so regressions are visible. For example, a team might agree that cold start on its reference budget phone must stay under a target the team chooses, and that the main list must scroll without visible dropped frames. Record these numbers for each release in a shared sheet. When a new feature pushes a number past its budget, the team discusses it before release rather than discovering it through poor reviews.

Lists and rendering

Long lists are the most common source of jank. Never render long data sets with a ScrollView and map, because that renders every item at once. Use FlatList or a community list such as FlashList, give each row a stable key, memoise row components and keep row layouts simple. If rows have a fixed height, provide a layout function so the list can skip measuring. Avoid creating new functions and objects in every render where it causes rows to re-render.

Check what is re-rendering before optimising. The React DevTools profiler can highlight components that render on every keystroke or scroll event. Often a single context provider near the root changes on every update and forces the whole tree to render. Splitting that context, or moving the changing value into a store with selectors, can fix a sluggish screen without touching the list at all.

typescript · illustrative
// Illustrative. See FlatList docs on reactnative.dev for all props.
import React, { memo, useCallback } from 'react';
import { FlatList, Text, View } from 'react-native';

type Order = { id: string; title: string };
const ROW_HEIGHT = 72;

const OrderRow = memo(function OrderRow({ item }: { item: Order }) {
  return (
    <View style={{ height: ROW_HEIGHT, justifyContent: 'center' }}>
      <Text>{item.title}</Text>
    </View>
  );
});

export function OrderList({ data }: { data: Order[] }) {
  const renderItem = useCallback(
    ({ item }: { item: Order }) => <OrderRow item={item} />, []);
  return (
    <FlatList
      data={data}
      keyExtractor={(o) => o.id}
      renderItem={renderItem}
      getItemLayout={(_, index) => ({
        length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index })}
      initialNumToRender={10}
      windowSize={5}
    />
  );
}

Startup time and bundle size

Startup time is the first impression. Check that your project uses the Hermes JavaScript engine, which is the default in current React Native and Expo projects and is designed for faster startup and lower memory use on Android. Keep the work done before the first screen small: load only essential data, defer analytics and non-critical SDK setup and avoid large synchronous storage reads on launch.

Watch bundle and asset size. Remove unused dependencies, prefer smaller libraries for dates and utilities, and compress images. Serve images at the size they are displayed rather than loading full-resolution photos into small thumbnails, which wastes both memory and data. Users on prepaid mobile data notice large downloads and updates, and some will uninstall an app that grows too heavy.

  • Measure cold start on a low-end device in a release build
  • Defer non-essential SDK setup until after the first screen
  • Resize and compress images, and cache them
  • Audit dependencies regularly and remove unused ones

Animations, memory and the JavaScript thread

React Native runs your JavaScript separately from the native UI, and when the JavaScript side is busy, touches and animations can lag. Run animations on the native side using the native driver option in the Animated API or a library such as Reanimated, so they stay smooth even when JavaScript is working. Move heavy calculations off the critical path, break large tasks into smaller pieces and avoid parsing very large JSON payloads on screens that need to feel instant.

Memory matters on phones with little RAM, where Android may kill your app in the background. Unsubscribe listeners and clear timers when components unmount, avoid keeping huge arrays in state and paginate large lists from the API. XDQ Labs teams run a short performance pass on a budget device before each release, checking startup, scrolling and memory, because users on those devices rarely report problems. They simply leave.

Key takeaways
  • Test on a real low-end Android device in a release build
  • Use virtualised lists with stable keys and memoised rows
  • Keep startup work small and confirm Hermes is enabled
  • Run animations on the native side to keep them smooth
  • Clean up listeners and paginate data to control memory
Try it

Install a release build of your app on the cheapest Android phone you can find. Measure cold start time with a stopwatch or profiler and record scrolling behaviour on your longest list. Apply two fixes from this lesson and measure again, recording the difference in your project notes.

Check your understanding

1. Why should performance be measured in a release build?

2. What is the main problem with rendering a long list using ScrollView and map?

3. How can you keep animations smooth when JavaScript is busy?

← PreviousReact Native Push Notifications, Deep Links and Payments: Key ConceptsNext lesson →React Native Testing and CI: Unit, Component and End-to-End Tests
XDQ xAgents · AI lab for your company
While you sleep, your AI agents keep working.

From USD 49 or INR 2,999 a month. A person approves every step.

Start free, 14 days →See every agent300 credits · no card needed · prices include taxes🚀 Startup? Free landing page for 30 days →Need custom software or a security review? Send a brief →