FlatList Performance: Stop Your List from Lagging

February 14, 2024 - 2 min read

FlatList is powerful but easy to mess up. If your list feels janky, here's what to fix.

The Symptoms

  • Scrolling feels choppy
  • Items flash or flicker
  • Blank spaces while scrolling fast
  • App freezes when loading the list

Fix #1: Add a keyExtractor

Never rely on index as key:

// Bad
<FlatList
  data={items}
  renderItem={renderItem}
/>
 
// Good
<FlatList
  data={items}
  renderItem={renderItem}
  keyExtractor={(item) => item.id.toString()}
/>

Fix #2: Memoize renderItem

This is the biggest performance win:

// Bad - creates new function every render
<FlatList
  data={items}
  renderItem={({ item }) => <Card item={item} />}
/>
 
// Good - memoized component
const MemoizedCard = React.memo(({ item }) => <Card item={item} />);
 
const renderItem = useCallback(
  ({ item }) => <MemoizedCard item={item} />,
  []
);
 
<FlatList
  data={items}
  renderItem={renderItem}
  keyExtractor={keyExtractor}
/>

Fix #3: Use getItemLayout for Fixed Heights

If all items are the same height:

const ITEM_HEIGHT = 80;
 
<FlatList
  data={items}
  renderItem={renderItem}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

This lets FlatList skip measurement and scroll instantly to any position.

Fix #4: Reduce Re-renders

Check what's causing re-renders:

// Bad - new object every render
<FlatList
  contentContainerStyle={{ padding: 16 }}
/>
 
// Good - stable reference
const contentStyle = useMemo(() => ({ padding: 16 }), []);
 
<FlatList
  contentContainerStyle={contentStyle}
/>

Fix #5: Optimize Images

Images are often the bottleneck:

// Use FastImage instead of Image
import FastImage from 'react-native-fast-image';
 
const Card = ({ item }) => (
  <FastImage
    source={{ uri: item.image, priority: FastImage.priority.normal }}
    style={styles.image}
    resizeMode={FastImage.resizeMode.cover}
  />
);

Fix #6: Windowing Props

Tune these based on your item size:

<FlatList
  data={items}
  renderItem={renderItem}
  initialNumToRender={10}        // Items to render initially
  maxToRenderPerBatch={10}       // Items per batch
  windowSize={5}                 // Render window multiplier
  removeClippedSubviews={true}   // Unmount off-screen items (Android)
  updateCellsBatchingPeriod={50} // Batch updates interval
/>

Fix #7: Avoid Inline Styles

// Bad
const renderItem = ({ item }) => (
  <View style={{ padding: 16, backgroundColor: '#fff' }}>
    <Text style={{ fontSize: 16 }}>{item.title}</Text>
  </View>
);
 
// Good
const styles = StyleSheet.create({
  container: { padding: 16, backgroundColor: '#fff' },
  title: { fontSize: 16 },
});
 
const renderItem = ({ item }) => (
  <View style={styles.container}>
    <Text style={styles.title}>{item.title}</Text>
  </View>
);

Fix #8: Use FlashList for Large Lists

For 1000+ items, consider Shopify's FlashList:

npm install @shopify/flash-list
import { FlashList } from '@shopify/flash-list';
 
<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={80}  // Required
/>

FlashList recycles views like native lists and handles most optimizations automatically.

Debugging Performance

Add this to see render counts:

const Card = React.memo(({ item }) => {
  console.log('Rendering:', item.id);
  return <View>...</View>;
});

If you see the same ID logging multiple times during scroll, something's triggering re-renders.

My Optimized FlatList Template

import React, { useCallback, useMemo } from 'react';
import { FlatList, StyleSheet } from 'react-native';
 
const ITEM_HEIGHT = 80;
 
const MyList = ({ data }) => {
  const keyExtractor = useCallback((item) => item.id.toString(), []);
 
  const getItemLayout = useCallback((_, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  }), []);
 
  const renderItem = useCallback(
    ({ item }) => <MemoizedItem item={item} />,
    []
  );
 
  const contentContainerStyle = useMemo(
    () => styles.contentContainer,
    []
  );
 
  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      getItemLayout={getItemLayout}
      contentContainerStyle={contentContainerStyle}
      initialNumToRender={10}
      maxToRenderPerBatch={10}
      windowSize={5}
      removeClippedSubviews
    />
  );
};
 
const MemoizedItem = React.memo(({ item }) => (
  // Your item component
));
 
const styles = StyleSheet.create({
  contentContainer: { padding: 16 },
});

Follow this pattern and your lists will be smooth.

© 2026 Rahul Mandyal. All rights reserved.