FlatList & Large Dataset Optimization
📖 Concept
FlatList is React Native's virtualized list component. It only renders items that are visible on screen (plus a configurable buffer), recycling off-screen items to save memory. However, out-of-the-box FlatList performance can be poor — you need to tune it.
Key FlatList props for performance:
| Prop | Purpose | Recommendation |
|---|---|---|
keyExtractor |
Unique key for reconciliation | Use stable IDs, NEVER index |
getItemLayout |
Skip measurement for fixed-height items | Always provide if possible |
windowSize |
Number of viewports to render | Default 21, reduce for less memory |
maxToRenderPerBatch |
Items rendered per batch | Default 10, increase for faster scroll |
initialNumToRender |
Items rendered on first pass | Set to visible count |
removeClippedSubviews |
Detach off-screen views | true (but can cause rendering bugs) |
updateCellsBatchingPeriod |
Delay between batch renders | Increase for smoother scroll |
FlashList (Shopify's replacement): FlashList is a drop-in replacement for FlatList that uses cell recycling instead of unmounting/remounting. It provides 5-10x better performance for large lists.
- Reuses native views instead of destroying and recreating them
- Requires
estimatedItemSizeprop - More memory-efficient for large lists
- Significantly better scroll performance
💻 Code Example
1// === OPTIMIZED FLATLIST ===23import { FlatList, View, Text } from 'react-native';45// 1. Memoized list item6const ProductItem = React.memo(function ProductItem({7 item,8 onPress9}: {10 item: Product;11 onPress: (id: string) => void;12}) {13 return (14 <Pressable onPress={() => onPress(item.id)} style={styles.item}>15 <FastImage16 source={{ uri: item.thumbnail }}17 style={styles.thumbnail}18 resizeMode="cover"19 />20 <View style={styles.info}>21 <Text style={styles.title}>{item.name}</Text>22 <Text style={styles.price}>{formatPrice(item.price)}</Text>23 </View>24 </Pressable>25 );26});2728// 2. Optimized FlatList configuration29const ITEM_HEIGHT = 80;30const SCREEN_HEIGHT = Dimensions.get('window').height;31const VISIBLE_ITEMS = Math.ceil(SCREEN_HEIGHT / ITEM_HEIGHT);3233function ProductList({ products }: { products: Product[] }) {34 const onPress = useCallback((id: string) => {35 navigation.navigate('ProductDetail', { id });36 }, [navigation]);3738 const renderItem = useCallback(({ item }: { item: Product }) => (39 <ProductItem item={item} onPress={onPress} />40 ), [onPress]);4142 const keyExtractor = useCallback((item: Product) => item.id, []);4344 // Fixed height layout — HUGE performance boost45 const getItemLayout = useCallback((46 _data: Product[] | null | undefined,47 index: number48 ) => ({49 length: ITEM_HEIGHT,50 offset: ITEM_HEIGHT * index,51 index,52 }), []);5354 return (55 <FlatList56 data={products}57 renderItem={renderItem}58 keyExtractor={keyExtractor}59 getItemLayout={getItemLayout}60 // Performance tuning61 initialNumToRender={VISIBLE_ITEMS}62 maxToRenderPerBatch={VISIBLE_ITEMS * 2}63 windowSize={5} // Render 5 viewports (2 above, current, 2 below)64 removeClippedSubviews={true}65 // Prevent flash of empty content during fast scroll66 maintainVisibleContentPosition={{67 minIndexForVisible: 0,68 }}69 />70 );71}7273// === FLASHLIST (SHOPIFY) — PREFERRED FOR LARGE LISTS ===7475import { FlashList } from '@shopify/flash-list';7677function OptimizedProductList({ products }: { products: Product[] }) {78 const renderItem = useCallback(({ item }: { item: Product }) => (79 <ProductItem item={item} onPress={onPress} />80 ), [onPress]);8182 return (83 <FlashList84 data={products}85 renderItem={renderItem}86 estimatedItemSize={ITEM_HEIGHT} // Required — enables cell recycling87 // FlashList handles most optimizations automatically:88 // - Cell recycling (reuses native views)89 // - Automatic batching90 // - Optimized scroll handling91 />92 );93}9495// === INFINITE SCROLL WITH PAGINATION ===9697function InfiniteList() {98 const [data, setData] = useState<Product[]>([]);99 const [page, setPage] = useState(1);100 const [loading, setLoading] = useState(false);101 const [hasMore, setHasMore] = useState(true);102103 const loadMore = useCallback(async () => {104 if (loading || !hasMore) return;105 setLoading(true);106107 try {108 const response = await api.getProducts({ page, limit: 20 });109 setData(prev => [...prev, ...response.items]);110 setHasMore(response.hasNextPage);111 setPage(p => p + 1);112 } catch (error) {113 // Don't reset data on error — keep what we have114 console.error('Failed to load more:', error);115 } finally {116 setLoading(false);117 }118 }, [page, loading, hasMore]);119120 return (121 <FlashList122 data={data}123 renderItem={renderItem}124 estimatedItemSize={80}125 onEndReached={loadMore}126 onEndReachedThreshold={0.5} // Trigger when 50% from bottom127 ListFooterComponent={loading ? <ActivityIndicator /> : null}128 />129 );130}
🏋️ Practice Exercise
List Optimization Exercises:
- Generate a list of 10,000 items and compare scroll performance between ScrollView, FlatList, and FlashList
- Implement
getItemLayoutfor a list with variable-height items (using estimated heights) - Build an infinite scroll list with pull-to-refresh and pagination
- Profile FlatList render counts with React DevTools — ensure only visible items re-render
- Implement a SectionList with sticky headers optimized for performance
- Create a horizontal card carousel using FlatList with snap-to-item behavior
⚠️ Common Mistakes
Using ScrollView for long lists — renders ALL items at once, crashes with 1000+ items due to memory
Not providing getItemLayout for fixed-height items — forces FlatList to measure each item, causing scroll jank
Creating new function references inside renderItem — defeats React.memo on list items, causing unnecessary re-renders
Setting windowSize too high — renders too many off-screen items, wasting memory and CPU
Not handling the 'key' properly — using index causes incorrect state association when data changes
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for FlatList & Large Dataset Optimization. Login to unlock this feature.