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:

  1. Native init: Defer native module initialization (TurboModules do this automatically)
  2. Runtime init: Use Hermes (AOT bytecode = faster than JSC's JIT compilation at startup)
  3. Bundle loading: Reduce bundle size, enable RAM bundles (inline requires)
  4. JS execution: Defer expensive initialization, use lazy imports
  5. Initial render: Render a skeleton UI first, load content async
  6. 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' not import 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

codeTap to expand ⛶
1// === STARTUP OPTIMIZATION ===
2
3// 1. Measure startup time
4const APP_START = global.performance?.now?.() ?? Date.now();
5
6function App() {
7 useEffect(() => {
8 const startupTime = (global.performance?.now?.() ?? Date.now()) - APP_START;
9 analytics.track('app_startup', { duration: startupTime });
10
11 if (startupTime > 3000) {
12 analytics.track('slow_startup', { duration: startupTime });
13 }
14 }, []);
15
16 return <AppNavigator />;
17}
18
19// 2. Lazy loading screens
20const HomeScreen = React.lazy(() => import('./screens/HomeScreen'));
21const ProfileScreen = React.lazy(() => import('./screens/ProfileScreen'));
22const SettingsScreen = React.lazy(() => import('./screens/SettingsScreen'));
23
24// In navigation — screen bundle only loads on first navigation
25function 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}
45
46// 3. Defer expensive initialization
47import { InteractionManager } from 'react-native';
48
49function AppBootstrap() {
50 useEffect(() => {
51 // Critical: initialize immediately
52 initializeAuth();
53 initializeNavigation();
54
55 // Non-critical: defer until app is interactive
56 InteractionManager.runAfterInteractions(() => {
57 initializeAnalytics();
58 initializeCrashReporting();
59 prefetchUserData();
60 warmImageCache();
61 });
62
63 // Low priority: defer even further
64 setTimeout(() => {
65 checkForUpdates();
66 syncOfflineData();
67 preloadNextScreens();
68 }, 5000);
69 }, []);
70}
71
72// 4. Bundle size: inline requires for large modules
73// Instead of importing at the top (executed at bundle load):
74// import { heavyChart } from 'react-native-charts';
75
76// Require when actually needed:
77function ChartScreen() {
78 const [ChartComponent, setChartComponent] = useState(null);
79
80 useEffect(() => {
81 // Only load the chart library when this screen mounts
82 const { LineChart } = require('react-native-charts');
83 setChartComponent(() => LineChart);
84 }, []);
85
86 if (!ChartComponent) return <ScreenSkeleton />;
87 return <ChartComponent data={chartData} />;
88}
89
90// 5. Memory-efficient image loading
91import FastImage from 'react-native-fast-image';
92
93// Preload critical images during startup
94FastImage.preload([
95 { uri: 'https://cdn.example.com/logo.png' },
96 { uri: 'https://cdn.example.com/default-avatar.png' },
97]);
98
99// In components — specify priority
100function FeedImage({ uri }: { uri: string }) {
101 return (
102 <FastImage
103 source={{
104 uri,
105 priority: FastImage.priority.normal,
106 cache: FastImage.cacheControl.immutable, // Cache forever
107 }}
108 style={styles.feedImage}
109 resizeMode={FastImage.resizeMode.cover}
110 />
111 );
112}
113
114// 6. State normalization for memory efficiency
115// ❌ Denormalized — duplicates data, wastes memory
116const 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};
123
124// ✅ Normalized — single source of truth, less memory
125const normalizedState = {
126 users: {
127 'u1': { id: 'u1', name: 'Alice', avatar: '...' }, // Stored once
128 },
129 posts: {
130 '1': { id: '1', text: '...', authorId: 'u1' },
131 '2': { id: '2', text: '...', authorId: 'u1' },
132 },
133 feedOrder: ['1', '2'], // Just IDs
134};

🏋️ Practice Exercise

Performance Optimization Exercises:

  1. Measure your app's startup time (native init → FMP) using the technique above
  2. Use react-native-bundle-visualizer to identify the largest modules in your bundle
  3. Implement lazy loading for a heavy screen and measure the startup time improvement
  4. Normalize a denormalized state and compare memory usage with Hermes profiler
  5. Implement a splash screen strategy that shows a skeleton while loading real data
  6. 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.