Startup Time, Bundle & Memory Optimization
📖 Concept
App startup time directly impacts user retention. Studies show 25% of users abandon an app if startup takes >3 seconds. For React Native, startup involves:
Startup timeline:
App Launch
→ Native initialization (Application.onCreate / AppDelegate)
→ React Native runtime initialization (Hermes/JSC)
→ JS bundle loading and execution
→ Root component mount
→ Initial render + layout
→ First meaningful paint (FMP)
Optimizing each phase:
- Native init: Defer native module initialization (TurboModules do this automatically)
- Runtime init: Use Hermes (AOT bytecode = faster than JSC's JIT compilation at startup)
- Bundle loading: Reduce bundle size, enable RAM bundles (inline requires)
- JS execution: Defer expensive initialization, use lazy imports
- Initial render: Render a skeleton UI first, load content async
- FMP: Prioritize above-the-fold content, defer below-the-fold
Bundle size optimization:
- Metro bundler tree-shaking is limited — be deliberate about imports
- Use
import { specific } from 'library'notimport library from 'library' - Analyze bundle with
react-native-bundle-visualizer - Lazy-load heavy screens with
React.lazy+ navigation - Consider separate bundles for different features (advanced)
Memory optimization strategies:
- Image caching with bounds (FastImage + maxDiskCache + maxMemoryCache)
- Virtualized lists (FlatList/FlashList) for all scrollable content
- Component unmounting for off-screen navigation stacks
- Ref-based data for non-rendering state (counters, timers, WebSocket connections)
- Normalized state to avoid data duplication
💻 Code Example
1// === STARTUP OPTIMIZATION ===23// 1. Measure startup time4const APP_START = global.performance?.now?.() ?? Date.now();56function App() {7 useEffect(() => {8 const startupTime = (global.performance?.now?.() ?? Date.now()) - APP_START;9 analytics.track('app_startup', { duration: startupTime });1011 if (startupTime > 3000) {12 analytics.track('slow_startup', { duration: startupTime });13 }14 }, []);1516 return <AppNavigator />;17}1819// 2. Lazy loading screens20const HomeScreen = React.lazy(() => import('./screens/HomeScreen'));21const ProfileScreen = React.lazy(() => import('./screens/ProfileScreen'));22const SettingsScreen = React.lazy(() => import('./screens/SettingsScreen'));2324// In navigation — screen bundle only loads on first navigation25function AppNavigator() {26 return (27 <Stack.Navigator>28 <Stack.Screen name="Home">29 {() => (30 <Suspense fallback={<ScreenSkeleton />}>31 <HomeScreen />32 </Suspense>33 )}34 </Stack.Screen>35 <Stack.Screen name="Profile">36 {() => (37 <Suspense fallback={<ScreenSkeleton />}>38 <ProfileScreen />39 </Suspense>40 )}41 </Stack.Screen>42 </Stack.Navigator>43 );44}4546// 3. Defer expensive initialization47import { InteractionManager } from 'react-native';4849function AppBootstrap() {50 useEffect(() => {51 // Critical: initialize immediately52 initializeAuth();53 initializeNavigation();5455 // Non-critical: defer until app is interactive56 InteractionManager.runAfterInteractions(() => {57 initializeAnalytics();58 initializeCrashReporting();59 prefetchUserData();60 warmImageCache();61 });6263 // Low priority: defer even further64 setTimeout(() => {65 checkForUpdates();66 syncOfflineData();67 preloadNextScreens();68 }, 5000);69 }, []);70}7172// 4. Bundle size: inline requires for large modules73// Instead of importing at the top (executed at bundle load):74// import { heavyChart } from 'react-native-charts';7576// Require when actually needed:77function ChartScreen() {78 const [ChartComponent, setChartComponent] = useState(null);7980 useEffect(() => {81 // Only load the chart library when this screen mounts82 const { LineChart } = require('react-native-charts');83 setChartComponent(() => LineChart);84 }, []);8586 if (!ChartComponent) return <ScreenSkeleton />;87 return <ChartComponent data={chartData} />;88}8990// 5. Memory-efficient image loading91import FastImage from 'react-native-fast-image';9293// Preload critical images during startup94FastImage.preload([95 { uri: 'https://cdn.example.com/logo.png' },96 { uri: 'https://cdn.example.com/default-avatar.png' },97]);9899// In components — specify priority100function FeedImage({ uri }: { uri: string }) {101 return (102 <FastImage103 source={{104 uri,105 priority: FastImage.priority.normal,106 cache: FastImage.cacheControl.immutable, // Cache forever107 }}108 style={styles.feedImage}109 resizeMode={FastImage.resizeMode.cover}110 />111 );112}113114// 6. State normalization for memory efficiency115// ❌ Denormalized — duplicates data, wastes memory116const feedState = {117 posts: [118 { id: '1', text: '...', author: { id: 'u1', name: 'Alice', avatar: '...' } },119 { id: '2', text: '...', author: { id: 'u1', name: 'Alice', avatar: '...' } },120 // Alice's data duplicated 100x if she has 100 posts!121 ],122};123124// ✅ Normalized — single source of truth, less memory125const normalizedState = {126 users: {127 'u1': { id: 'u1', name: 'Alice', avatar: '...' }, // Stored once128 },129 posts: {130 '1': { id: '1', text: '...', authorId: 'u1' },131 '2': { id: '2', text: '...', authorId: 'u1' },132 },133 feedOrder: ['1', '2'], // Just IDs134};
🏋️ Practice Exercise
Performance Optimization Exercises:
- Measure your app's startup time (native init → FMP) using the technique above
- Use
react-native-bundle-visualizerto identify the largest modules in your bundle - Implement lazy loading for a heavy screen and measure the startup time improvement
- Normalize a denormalized state and compare memory usage with Hermes profiler
- Implement a splash screen strategy that shows a skeleton while loading real data
- Create a memory budget for your app (target: <150MB on mid-range devices) and track it
⚠️ Common Mistakes
Importing large libraries at the top of entry files — entire library is parsed and executed at startup even if unused
Not using Hermes — JSC compiles JS at runtime, adding 500ms+ to startup time
Storing large image bitmaps in JS state — use native image caching (FastImage) and avoid base64 data URIs
Denormalized state that duplicates data — 100 posts by the same user stores the user object 100 times
Not measuring — optimizing without profiling is guessing; always measure before and after changes
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Startup Time, Bundle & Memory Optimization. Login to unlock this feature.