# React Native FlatList Performance Optimization: What Actually Works

> Practical techniques to fix slow React Native FlatList scroll performance — FlashList, getItemLayout, memoization, and profiling explained.

- **Published:** 2025-05-02
- **Author:** Clixo
- **Reading time:** 5 min read
- **Tags:** react-native, performance, flatlist, mobile, optimization
- **Canonical URL:** https://clixo.sh/blog/react-native-flatlist-performance-optimization

Dropped frames during list scrolling are one of the first things users notice and one of the last things developers investigate properly. If your React Native app has a FlatList that stutters, freezes, or blanks out cells while scrolling, the problem is almost always one of a small set of measurable causes — not a fundamental React Native limitation.

This guide covers the techniques that consistently produce the largest improvements, in the order you should apply them.

## Profile Before You Touch Code

```mermaid
flowchart TD
  A["Profile scroll on mid-tier Android"] --> B{"JS thread frame drops?"}
  B -->|Yes| C["Reduce renderItem work"]
  C --> D["Wrap item component in React.memo"]
  D --> E["Wrap renderItem in useCallback"]
  B -->|No| F{"UI thread frame drops?"}
  F -->|Yes| G["Implement getItemLayout for fixed rows"]
  G --> H["Switch FlatList to FlashList"]
  F -->|No| I["Tune initialNumToRender and windowSize"]
  E --> J["Profile again — record new FPS"]
  H --> J
  I --> J
```

Every optimization effort should start with a number, not a guess. Open the React Native DevTools Performance tab or enable Systrace and record a scroll session on a mid-tier Android device. Look for two things:

- **JS thread frame time**: if frames are being dropped here, the render function is doing too much work
- **UI thread frame time**: if drops are here, something is forcing layout measurement on the UI thread

Write down your baseline FPS before making any changes. "Feels slow" is not a metric.

## The Highest-Impact React Native FlatList Optimization: getItemLayout

If you know the height of each row in advance, implement `getItemLayout`. This single prop eliminates the measurement pass that FlatList otherwise runs for every item as it enters the viewport.

```js
getItemLayout={(data, index) => ({
  length: ITEM_HEIGHT,
  offset: ITEM_HEIGHT * index,
  index,
})}
```

For variable-height lists this is not directly applicable, but for feeds, messages, product grids, and settings screens with fixed rows, this is the change with the highest return per line of code.

## Switch to FlashList for Complex Lists

Shopify's FlashList is a drop-in FlatList replacement that uses cell recycling instead of React component recycling. In most scenarios it renders significantly faster than FlatList with equivalent configuration.

The API is intentionally close to FlatList. The key difference is that you must provide `estimatedItemSize`, which gives FlashList the information it needs to pre-allocate the recycling pool correctly.

```js
import { FlashList } from "@shopify/flash-list";

// Replace FlatList with FlashList
// Add estimatedItemSize based on your typical item height
```

Run the Flashlist performance check (`npx @shopify/flash-list check`) to confirm your estimations are close — poor `estimatedItemSize` values reduce the benefit.

## Memoize List Item Components

By default, React re-renders a list item component any time the parent re-renders, even if that item's data has not changed. Wrap your item component in `React.memo`:

```js
const ListItem = React.memo(({ item }) => {
  return (
    // your item JSX
  );
});
```

Also memoize the `renderItem` prop itself using `useCallback`, so FlatList receives a stable function reference:

```js
const renderItem = useCallback(({ item }) => (
  <ListItem item={item} />
), []);
```

Without `useCallback`, every parent render creates a new `renderItem` function reference, which bypasses the `React.memo` optimization on the item component.

## Set the Right FlatList Window Configuration

FlatList renders a window of items around the current scroll position. The defaults are tuned for a general case, not for your specific list. The props that matter most:

- `initialNumToRender`: number of items rendered on first paint. Set this to exactly the number visible on screen — not more, not less.
- `maxToRenderPerBatch`: number of items rendered per incremental batch during scroll. Lower values reduce JS thread pressure per frame.
- `windowSize`: the total render window in multiples of the visible area. Reducing this from the default of 21 to 5-7 cuts memory usage significantly on long lists.
- `removeClippedSubviews`: set to `true` on Android for lists with complex item layouts. Has less impact on iOS.

There is no universal value for these props. Profile after each change.

## Avoid Inline Functions and Object Literals in renderItem

Patterns like this create new objects on every render:

```js
// Avoid this
renderItem={({ item }) => <Item style={{ margin: 8 }} onPress={() => navigate(item.id)} />}
```

Every new object reference breaks shallow comparison in `React.memo`. Pull styles into a `StyleSheet.create` call and extract handlers with `useCallback`.

## Keep Images Lightweight

List performance degrades fast when images are large or unoptimized. Use `react-native-fast-image` for HTTP images — it provides persistent disk caching and avoids the re-fetch and re-decode that the built-in `Image` component does when an item re-mounts.

Serve images at the size they will be displayed. Sending a 1200px image into a 120px cell is both a bandwidth and a decode-time problem.

## Paginate, Do Not Load Everything Upfront

Use the `onEndReached` and `onEndReachedThreshold` props to load data in pages. A list with 20-30 visible items and pagination is fundamentally faster than a list with 500 items pre-loaded. No amount of windowing configuration compensates for an unbounded item count.

## Avoid Nested FlatLists Where Possible

A FlatList inside a ScrollView disables virtualization on the inner list. The React Native docs explicitly warn against this. If your layout calls for a scrollable container with one or more scrollable lists inside it, refactor to a single FlatList using `ListHeaderComponent`, `ListFooterComponent`, and `SectionList` for grouped data.

## Checklist Before You Ship

- Baseline FPS recorded on a physical mid-tier Android device
- `getItemLayout` implemented for fixed-height rows
- Item components wrapped in `React.memo`
- `renderItem` wrapped in `useCallback`
- `initialNumToRender` matched to visible item count
- Images served at display size and cached with fast-image
- Pagination implemented via `onEndReached`
- No nested FlatLists inside ScrollViews

Smooth list scrolling is a requirement, not a nice-to-have. If your team is working through a larger performance overhaul — FlatList, animations, cold start, or all three — [talk to Clixo](https://clixo.sh/#contact) about scoping the work.

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
