React Native lets you build iOS and Android apps using JavaScript and React. Interviews cover architecture, component lifecycle, navigation, state management, performance, and native integrations. This guide provides 50 common React Native interview questions with detailed answers and code examples.
Table of Contents
- Architecture & Core Concepts
- Components & Styling
- Navigation
- State Management
- Networking & Data
- Performance Optimization
- Native Modules & Platform APIs
- Expo vs Bare React Native
- Testing
- Advanced Topics
Architecture & Core Concepts
1. How does React Native work under the hood?
React Native runs JavaScript in a JS engine (Hermes on Android, JavaScriptCore on iOS) and communicates with native components via a bridge (old architecture) or JSI (new architecture).
Old Architecture (Bridge):
- Async JSON messages cross the bridge between JS thread and native thread
- Three threads: JS thread, UI thread (main), native modules thread
New Architecture (JSI — JavaScript Interface):
- Direct C++ references — no serialization overhead
- Synchronous calls to native modules
- Fabric renderer replaces the old UIManager
JavaScript Thread (Hermes)
│
│ JSI (C++ bindings, synchronous)
│
Native Threads (UI, background)
2. What is the difference between React Native and React?
| Feature | React | React Native |
|---|---|---|
| Target | Web browsers | iOS & Android |
| Rendering | HTML DOM via ReactDOM | Native components via bridge/JSI |
| Styling | CSS classes + styled-components | StyleSheet API (subset of CSS) |
| Navigation | React Router | React Navigation / Expo Router |
| Components | <div>, <span>, <img> |
<View>, <Text>, <Image> |
| Build output | HTML/JS/CSS bundle | Native binary (.ipa / .apk) |
| Hot Reload | Yes | Yes (Fast Refresh) |
React Native reuses React's component model and hooks but renders native UI instead of DOM elements.
3. What is JSI (JavaScript Interface) and why is it important?
JSI is a C++ API that allows JavaScript to hold references to native objects directly — without serializing to JSON and crossing an async bridge.
Benefits over the old bridge:
- Synchronous calls — no async round-trips
- Type safety — native types exposed to JS directly
- Performance — eliminates JSON serialization bottleneck
- TurboModules — lazy-loaded native modules via JSI
- Fabric — new renderer built on JSI
// Old bridge: async, JSON-serialized
NativeModules.MyModule.doSomething(data, callback);
// JSI / TurboModules: synchronous C++ call
const result = global.__myNativeModule.doSomething(data);
4. What threads does React Native use?
| Thread | Purpose |
|---|---|
| JS thread | Runs JavaScript (React logic, state, event handlers) |
| Main/UI thread | Renders native views, handles gestures |
| Native modules thread | Async native module calls (network, storage, etc.) |
| Background threads | Image decoding, network I/O (managed by the OS) |
The JS thread and UI thread are separate — a slow JS computation won't block scroll animations, but a heavy JS workload can still cause dropped frames if it starves the JS thread.
5. What is Hermes and why does React Native use it?
Hermes is an open-source JavaScript engine optimised for React Native, developed by Meta.
Advantages over JavaScriptCore:
- Ahead-of-time (AOT) bytecode compilation — faster startup
- Smaller memory footprint
- Better performance on low-end Android devices
- Better source map support for debugging
// android/app/build.gradle
project.ext.react = [
enableHermes: true // default in RN 0.70+
]
Hermes is now the default engine for both Android and iOS in React Native 0.70+.
Components & Styling
6. What are the core components in React Native?
| Component | Purpose | Web equivalent |
|---|---|---|
View |
Container, layout | <div> |
Text |
Display text | <p>, <span> |
Image |
Display images | <img> |
TextInput |
Text entry field | <input> |
ScrollView |
Scrollable container | <div style="overflow:scroll"> |
FlatList |
Virtualized list | Virtual scroller |
SectionList |
Grouped list with headers | — |
TouchableOpacity |
Tappable wrapper with opacity | <button> |
Pressable |
Flexible tappable wrapper | <button> |
Modal |
Overlay modal | Dialog / portal |
ActivityIndicator |
Loading spinner | CSS spinner |
7. How does styling work in React Native?
React Native uses a StyleSheet API instead of CSS. Styles are JavaScript objects using camelCase properties.
import { StyleSheet, View, Text } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 8,
},
});
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello React Native</Text>
</View>
);
}
Key differences from CSS:
- No inheritance (except
Textinheriting text styles from parentText) - Flexbox is the default layout (column direction by default)
- No shorthand
margin: 8 16— must usemarginVertical/marginHorizontal - Numbers (not strings) for
fontSize,padding, etc.
8. How does Flexbox work in React Native?
React Native uses Flexbox for layout, but with one key difference: flexDirection defaults to 'column' (not 'row' as in CSS).
const styles = StyleSheet.create({
row: {
flexDirection: 'row', // horizontal
justifyContent: 'space-between', // main axis
alignItems: 'center', // cross axis
},
box: {
flex: 1, // take equal space
height: 50,
backgroundColor: 'tomato',
marginHorizontal: 4,
},
});
| Property | Default in RN | Default in CSS |
|---|---|---|
flexDirection |
'column' |
'row' |
alignContent |
'flex-start' |
'normal' |
flexShrink |
0 |
1 |
9. What is the difference between ScrollView and FlatList?
| Feature | ScrollView | FlatList |
|---|---|---|
| Rendering | Renders all children at once | Virtualised — only renders visible items |
| Performance | Poor with many items | Optimised for large datasets |
| Use case | Short content / fixed screens | Long lists (contacts, feeds, etc.) |
| Pull-to-refresh | Manual | Built-in onRefresh prop |
| Horizontal scrolling | horizontal prop |
horizontal prop |
| Separator | Manual | ItemSeparatorComponent prop |
// FlatList — virtualised, efficient for 1000s of items
<FlatList
data={items}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => <ItemCard item={item} />}
ItemSeparatorComponent={() => <View style={styles.separator} />}
onRefresh={handleRefresh}
refreshing={isRefreshing}
/>
10. What is Pressable and how does it differ from TouchableOpacity?
Pressable is the modern, flexible touch handler introduced in React Native 0.63. It replaces TouchableOpacity, TouchableHighlight, and TouchableNativeFeedback.
// TouchableOpacity (older)
<TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
<Text>Press me</Text>
</TouchableOpacity>
// Pressable (modern, preferred)
<Pressable
onPress={handlePress}
onLongPress={handleLongPress}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed,
]}
>
{({ pressed }) => (
<Text style={pressed ? styles.textPressed : styles.text}>
{pressed ? 'Pressed!' : 'Press me'}
</Text>
)}
</Pressable>
Pressable supports render props with pressed state and hitSlop / delayLongPress without wrapping HOCs.
Navigation
11. What is React Navigation and what are its main navigators?
React Navigation is the standard navigation library for React Native. It provides stack, tab, drawer, and other navigators.
| Navigator | Use case | Component |
|---|---|---|
| Stack | Push/pop screens (like iOS nav) | createNativeStackNavigator |
| Bottom Tab | Tab bar at the bottom | createBottomTabNavigator |
| Material Top Tab | Tabs at the top with swipe | createMaterialTopTabNavigator |
| Drawer | Sidebar menu | createDrawerNavigator |
| Modal | Full-screen overlay | Stack with presentation: 'modal' |
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
12. How do you pass data between screens in React Navigation?
// Passing params when navigating
navigation.navigate('Profile', {
userId: 42,
name: 'Alice',
});
// Receiving params in destination screen
function ProfileScreen({ route, navigation }) {
const { userId, name } = route.params;
return (
<View>
<Text>User: {name} (ID: {userId})</Text>
<Button
title="Go back"
onPress={() => navigation.goBack()}
/>
</View>
);
}
For complex cross-screen state, prefer a state manager (Zustand, Redux) over params chaining.
13. What is Expo Router and how does it differ from React Navigation?
Expo Router is a file-based routing library (similar to Next.js App Router) built on top of React Navigation.
| Feature | React Navigation | Expo Router |
|---|---|---|
| Route definition | Imperative code | File system (app/ folder) |
| Deep linking | Manual config | Automatic |
| TypeScript routes | Manual typing | Auto-generated types |
| Universal | React Native only | Web + native (universal links) |
| Setup | Manual | Built into Expo |
app/
_layout.tsx ← root layout (NavigationContainer)
index.tsx ← "/" or home screen
profile/
[id].tsx ← "/profile/123" with dynamic segment
(tabs)/
_layout.tsx ← tab navigator
home.tsx
settings.tsx
14. How do you implement deep linking in React Native?
Deep linking lets URLs open specific screens in your app.
// react-navigation linking config
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: '',
Profile: 'profile/:userId',
Settings: 'settings',
},
},
};
<NavigationContainer linking={linking}>
{/* navigators */}
</NavigationContainer>
On iOS, configure Associated Domains in Xcode. On Android, add intent filters in AndroidManifest.xml.
State Management
15. What state management options are available in React Native?
| Library | Best for | Key API |
|---|---|---|
useState + useContext |
Small apps, simple global state | React built-ins |
| Zustand | Simple global state, minimal boilerplate | create, useStore |
| Redux Toolkit | Large apps, complex state, DevTools | configureStore, createSlice |
| Jotai | Atomic state, component-level granularity | atom, useAtom |
| React Query / TanStack Query | Server state (cache, sync, loading) | useQuery, useMutation |
| MobX | Observable reactive state | makeAutoObservable |
16. How do you use Zustand in React Native?
import { create } from 'zustand';
// Define store
const useAuthStore = create((set) => ({
user: null,
token: null,
login: (user, token) => set({ user, token }),
logout: () => set({ user: null, token: null }),
}));
// Use in component
function ProfileScreen() {
const { user, logout } = useAuthStore();
return (
<View>
<Text>Welcome, {user?.name}</Text>
<Button title="Logout" onPress={logout} />
</View>
);
}
For persistence, combine with zustand/middleware's persist and AsyncStorage.
17. How do you handle async state (loading, error, data) in React Native?
import { useState, useEffect } from 'react';
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetchUser(userId)
.then((data) => {
if (!cancelled) setUser(data);
})
.catch((err) => {
if (!cancelled) setError(err.message);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => { cancelled = true; }; // cleanup for unmount
}, [userId]);
return { user, loading, error };
}
With TanStack Query this is much simpler:
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
18. What is the Context API and when should you use it over a state library?
// Create context
const ThemeContext = React.createContext('light');
// Provider
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</ThemeContext.Provider>
);
}
// Consumer
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<Pressable
style={{ backgroundColor: theme === 'dark' ? '#333' : '#fff' }}
onPress={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<Text>Toggle Theme</Text>
</Pressable>
);
}
Use Context when: theme, locale, auth session — infrequently changing, app-wide values.
Use Zustand/Redux when: frequent updates, complex derived state, DevTools needed, or avoiding re-render issues.
Networking & Data
19. How do you make API calls in React Native?
React Native ships with the Fetch API. Use axios for interceptors and timeout support.
// Fetch API
async function getUsers() {
const response = await fetch('https://api.example.com/users', {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
// Axios (with base URL + interceptors)
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
});
api.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
});
const { data: users } = await api.get('/users');
20. How do you store data locally in React Native?
| Storage | Use case | API |
|---|---|---|
AsyncStorage |
Simple key-value, non-sensitive | setItem, getItem, removeItem |
SecureStore (Expo) |
Sensitive data (tokens, passwords) | Keychain (iOS) / Keystore (Android) |
SQLite (expo-sqlite) |
Structured relational data | SQL queries |
| MMKV | High-performance key-value | Synchronous, much faster than AsyncStorage |
| Realm / WatermelonDB | Complex local DB with sync | Object-based queries |
import AsyncStorage from '@react-native-async-storage/async-storage';
// Save
await AsyncStorage.setItem('user_prefs', JSON.stringify({ theme: 'dark' }));
// Read
const raw = await AsyncStorage.getItem('user_prefs');
const prefs = raw ? JSON.parse(raw) : null;
// Delete
await AsyncStorage.removeItem('user_prefs');
Performance Optimization
21. What causes performance issues in React Native?
| Problem | Cause | Fix |
|---|---|---|
| Dropped frames (jank) | Heavy JS work on JS thread | Move to native thread / worklets |
| Slow list scrolling | Non-virtualised list | Use FlatList, not ScrollView |
| Excessive re-renders | Incorrect state structure | React.memo, useMemo, useCallback |
| Large bundle | Unused imports | Tree shaking, code splitting |
| Slow image loading | Large unoptimised images | FastImage, cache headers |
| JS thread blocked | Synchronous heavy computation | InteractionManager, Worklets |
| Bridge congestion | Many JS↔native calls | Batch calls, use animations on native thread |
22. How does React.memo help performance?
// Without memo: re-renders every time parent re-renders
function ExpensiveItem({ item, onPress }) {
console.log('rendering', item.id);
return <Pressable onPress={() => onPress(item.id)}><Text>{item.name}</Text></Pressable>;
}
// With memo: only re-renders if props change
const ExpensiveItem = React.memo(function ExpensiveItem({ item, onPress }) {
return <Pressable onPress={() => onPress(item.id)}><Text>{item.name}</Text></Pressable>;
});
// In parent: stabilise callback with useCallback
function List({ items }) {
const handlePress = useCallback((id) => {
console.log('pressed', id);
}, []); // stable reference
return (
<FlatList
data={items}
renderItem={({ item }) => (
<ExpensiveItem item={item} onPress={handlePress} />
)}
/>
);
}
23. What is Reanimated and when should you use it?
React Native Reanimated (v2+) allows animations to run on the UI thread using worklets — eliminating bridge overhead.
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
} from 'react-native-reanimated';
function AnimatedCard() {
const scale = useSharedValue(1);
const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
return (
<Pressable
onPressIn={() => { scale.value = withSpring(0.95); }}
onPressOut={() => { scale.value = withSpring(1); }}
>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Press me</Text>
</Animated.View>
</Pressable>
);
}
Use Reanimated for: gesture-driven animations, 60fps scroll effects, complex shared element transitions. Use the Animated API for simple one-shot animations.
24. How do you optimise FlatList performance?
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ItemCard item={item} />}
// Performance props
removeClippedSubviews={true} // unmount off-screen views (Android)
maxToRenderPerBatch={10} // items rendered per batch
windowSize={5} // render window (x visible heights)
initialNumToRender={10} // first render item count
getItemLayout={(data, index) => ({ // skip measure if fixed height
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
// Interaction
onEndReached={loadMore}
onEndReachedThreshold={0.5}
/>
Always define getItemLayout for fixed-height items — it prevents React Native from measuring each item on render.
25. What is InteractionManager and when do you use it?
InteractionManager defers heavy work until after animations complete, preventing jank.
import { InteractionManager } from 'react-native';
import { useEffect } from 'react';
function HeavyScreen() {
const [data, setData] = useState(null);
useEffect(() => {
// Wait for screen transition animation to finish
const interaction = InteractionManager.runAfterInteractions(() => {
const result = computeHeavyData(); // runs after animation
setData(result);
});
return () => interaction.cancel();
}, []);
return data ? <DataView data={data} /> : <ActivityIndicator />;
}
Native Modules & Platform APIs
26. What are Native Modules and how do you use them?
Native Modules expose platform APIs (Bluetooth, camera, sensors) that don't exist in React Native's JS API.
JavaScript side:
import { NativeModules } from 'react-native';
const { BatteryModule } = NativeModules;
async function checkBattery() {
const level = await BatteryModule.getBatteryLevel();
console.log(`Battery: ${Math.round(level * 100)}%`);
}
Android side (Kotlin):
class BatteryModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName() = "BatteryModule"
@ReactMethod
fun getBatteryLevel(promise: Promise) {
val bm = reactContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val level = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
promise.resolve(level / 100.0)
}
}
With the new architecture, use TurboModules and Codegen for type-safe native modules.
27. How do you handle platform-specific code?
import { Platform } from 'react-native';
// Platform.OS check
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 50 : 20,
},
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
},
android: {
elevation: 4,
},
}),
});
// Platform.Version check
if (Platform.OS === 'android' && Platform.Version < 21) {
// handle older Android
}
Platform-specific files:
Button.ios.tsx ← used on iOS
Button.android.tsx ← used on Android
Button.tsx ← fallback for web / unknown
28. How do you request permissions in React Native?
Using react-native-permissions (recommended) or Expo's expo-permissions:
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
async function requestCamera() {
const permission = Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA,
});
const result = await request(permission);
switch (result) {
case RESULTS.GRANTED:
openCamera();
break;
case RESULTS.DENIED:
Alert.alert('Camera permission denied');
break;
case RESULTS.BLOCKED:
Alert.alert('Open Settings to enable camera access');
Linking.openSettings();
break;
}
}
29. How do you use the camera in React Native?
Using expo-camera (Expo) or react-native-vision-camera (bare, high performance):
import { CameraView, useCameraPermissions } from 'expo-camera';
function CameraScreen() {
const [permission, requestPermission] = useCameraPermissions();
if (!permission?.granted) {
return (
<View>
<Text>Camera access needed</Text>
<Button title="Grant Permission" onPress={requestPermission} />
</View>
);
}
return (
<CameraView
style={StyleSheet.absoluteFill}
facing="back"
onBarcodeScanned={({ data }) => console.log('QR:', data)}
/>
);
}
30. How do you handle push notifications in React Native?
Using Expo Notifications (Expo) or Firebase Cloud Messaging (bare):
import * as Notifications from 'expo-notifications';
// Request permission
const { status } = await Notifications.requestPermissionsAsync();
// Get push token (Expo's managed service)
const token = await Notifications.getExpoPushTokenAsync({
projectId: Constants.expoConfig.extra.eas.projectId,
});
// Listen for notifications
useEffect(() => {
const sub = Notifications.addNotificationReceivedListener((notification) => {
console.log('Received:', notification);
});
const tapSub = Notifications.addNotificationResponseReceivedListener((response) => {
const { screen } = response.notification.request.content.data;
navigation.navigate(screen);
});
return () => { sub.remove(); tapSub.remove(); };
}, []);
Expo vs Bare React Native
31. What is Expo and what are its advantages?
Expo is a framework and platform built on top of React Native that simplifies development.
| Feature | Expo (Managed) | Bare React Native |
|---|---|---|
| Setup | npx create-expo-app |
npx @react-native-community/cli init |
| Native builds | EAS Build (cloud) | Local Xcode / Android Studio |
| OTA updates | Expo Updates | Manual or CodePush |
| Native modules | expo-* modules only | Any npm native module |
| Flexibility | Lower (managed) | Full control |
| Xcode/Android Studio required | No | Yes |
| File-based routing | Expo Router | React Navigation (manual) |
32. What is EAS (Expo Application Services)?
EAS is Expo's cloud build and submission platform:
- EAS Build — cloud CI/CD for iOS and Android builds without local Xcode/Android Studio
- EAS Submit — automated App Store / Google Play submission
- EAS Update — over-the-air JS bundle updates (without app store review)
# Build for production
eas build --platform all
# Submit to stores
eas submit --platform all
# Push OTA update
eas update --branch production --message "Bug fix"
33. What is the difference between Expo Go and a development build?
| Feature | Expo Go | Development Build |
|---|---|---|
| Purpose | Quick prototyping | Production-like testing |
| Custom native modules | Not supported | Fully supported |
| Build required | No | Yes (once, via EAS) |
| Config plugins | Limited | Full support |
| App Store distribution | No | Yes |
Use Expo Go for learning and simple projects. Switch to a development build when you need custom native code.
Testing
34. What are the testing levels in React Native?
| Level | Tool | What it tests |
|---|---|---|
| Unit | Jest | Pure functions, utilities, hooks |
| Component | React Native Testing Library | Component rendering, interactions |
| E2E | Detox / Maestro | Full user flows on real device/emulator |
35. How do you write component tests with React Native Testing Library?
import { render, screen, fireEvent } from '@testing-library/react-native';
import LoginForm from './LoginForm';
describe('LoginForm', () => {
it('shows error when email is empty', () => {
render(<LoginForm onLogin={jest.fn()} />);
fireEvent.press(screen.getByRole('button', { name: /login/i }));
expect(screen.getByText('Email is required')).toBeTruthy();
});
it('calls onLogin with credentials', () => {
const onLogin = jest.fn();
render(<LoginForm onLogin={onLogin} />);
fireEvent.changeText(screen.getByPlaceholderText('Email'), 'user@example.com');
fireEvent.changeText(screen.getByPlaceholderText('Password'), 'secret123');
fireEvent.press(screen.getByRole('button', { name: /login/i }));
expect(onLogin).toHaveBeenCalledWith({
email: 'user@example.com',
password: 'secret123',
});
});
});
36. How do you mock native modules in Jest?
// jest.setup.js — mock AsyncStorage
jest.mock('@react-native-async-storage/async-storage', () =>
require('@react-native-async-storage/async-storage/jest/async-storage-mock')
);
// Mock a custom native module
jest.mock('react-native', () => {
const RN = jest.requireActual('react-native');
RN.NativeModules.BatteryModule = {
getBatteryLevel: jest.fn().mockResolvedValue(0.85),
};
return RN;
});
// package.json
{
"jest": {
"preset": "react-native",
"setupFilesAfterFramework": ["./jest.setup.js"]
}
}
Advanced Topics
37. What is the New Architecture in React Native?
The New Architecture replaces the bridge with JSI and introduces:
| Component | Old | New |
|---|---|---|
| Native modules | Bridge (async JSON) | TurboModules (JSI, synchronous) |
| Renderer | UIManager / Shadow tree | Fabric (synchronous, concurrent) |
| Layout engine | Yoga (separate thread) | Yoga on UIThread via Fabric |
| State updates | Batched async | Concurrent React (transitions) |
Enable in React Native 0.71+:
// android/gradle.properties
newArchEnabled=true
# ios/Podfile
:fabric_enabled => true
38. How does React Native handle gestures?
Using React Native Gesture Handler (replaces built-in responder system):
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
function DraggableBox() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const drag = Gesture.Pan()
.onUpdate((event) => {
translateX.value = event.translationX;
translateY.value = event.translationY;
})
.onEnd(() => {
translateX.value = withSpring(0);
translateY.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
],
}));
return (
<GestureDetector gesture={drag}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}
39. How do you implement code splitting / lazy loading in React Native?
import React, { Suspense, lazy } from 'react';
import { ActivityIndicator } from 'react-native';
// Lazy load heavy screen
const HeavyScreen = lazy(() => import('./HeavyScreen'));
function Navigator() {
return (
<Suspense fallback={<ActivityIndicator />}>
<HeavyScreen />
</Suspense>
);
}
With React Navigation, use lazy option on navigators to defer screen component loading.
40. What is Metro and how does it differ from webpack?
Metro is React Native's JavaScript bundler.
| Feature | Metro | Webpack |
|---|---|---|
| Target | React Native (mobile) | Web browsers |
| Speed | Optimised for RN (incremental) | Configurable |
| Config | metro.config.js |
webpack.config.js |
| Code splitting | Basic | Advanced (dynamic import) |
| Hot reload | Fast Refresh | HMR |
| Tree shaking | Limited | Full |
41. How do you handle environment variables in React Native?
React Native doesn't have a built-in .env loader. Common approaches:
// Using react-native-config (recommended)
// .env
API_URL=https://api.example.com
APP_ENV=production
// Usage
import Config from 'react-native-config';
const apiUrl = Config.API_URL;
With Expo:
// app.config.js
export default {
extra: {
apiUrl: process.env.API_URL ?? 'http://localhost:3000',
},
};
// Usage
import Constants from 'expo-constants';
const { apiUrl } = Constants.expoConfig.extra;
Never put secrets in environment variables accessible to the JS bundle — they can be extracted from the APK/IPA. Use a backend to proxy sensitive calls.
42. How do you handle offline mode in React Native?
import NetInfo from '@react-native-community/netinfo';
import { useEffect, useState } from 'react';
function useNetworkStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
setIsOnline(state.isConnected ?? false);
});
return unsubscribe;
}, []);
return isOnline;
}
// Usage
function App() {
const isOnline = useNetworkStatus();
return (
<View>
{!isOnline && <OfflineBanner />}
<MainContent />
</View>
);
}
For offline-first apps, combine with TanStack Query's persistQueryClient + AsyncStorage, or WatermelonDB for full offline sync.
43. What is CodePush and what are OTA updates?
OTA (over-the-air) updates push JS bundle changes directly to users without an app store review.
| Service | Provider | Works with |
|---|---|---|
| CodePush | Microsoft App Center | Bare React Native |
| Expo Updates | Expo | Expo managed + bare |
| EAS Update | Expo | Expo projects |
// EAS Update: push OTA update
eas update --branch production --message "Fix login bug"
Limitations: OTA can only update JavaScript and assets. Native code changes always require a full app store release.
44. How do you debug React Native apps?
| Tool | Use case |
|---|---|
| Flipper | Network, storage, layout inspection, Redux DevTools |
| React DevTools | Component tree, props, hooks inspection |
| Chrome DevTools | JS debugger (via remote debugging) |
| Hermes Profiler | JS performance profiling |
| Android Studio Profiler | Memory, CPU, network on Android |
| Xcode Instruments | Memory, CPU, GPU on iOS |
console.log |
Quick value inspection |
# Start Metro with dev tools
npx react-native start
# Enable remote debugging
# Shake device or Cmd+D → "Debug with Chrome"
45. What is Turbo Modules?
TurboModules replace the old bridge-based Native Modules in the New Architecture.
Key improvements:
- Lazy initialization — modules load on first use, not at startup
- Synchronous calls — direct C++ calls via JSI (no async bridge)
- Type safety — Codegen generates interfaces from Flow/TypeScript specs
// Native module spec (TypeScript)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getBatteryLevel(): Promise<number>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('BatteryModule');
46. How do you handle app state and background tasks?
import { AppState } from 'react-native';
import { useEffect, useRef } from 'react';
function useAppState(onForeground, onBackground) {
const appState = useRef(AppState.currentState);
useEffect(() => {
const sub = AppState.addEventListener('change', (nextState) => {
if (appState.current.match(/inactive|background/) && nextState === 'active') {
onForeground?.();
} else if (nextState.match(/inactive|background/)) {
onBackground?.();
}
appState.current = nextState;
});
return () => sub.remove();
}, []);
}
// Background tasks with expo-background-fetch
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
TaskManager.defineTask('BACKGROUND_SYNC', async () => {
await syncData();
return BackgroundFetch.BackgroundFetchResult.NewData;
});
await BackgroundFetch.registerTaskAsync('BACKGROUND_SYNC', {
minimumInterval: 15 * 60, // 15 minutes
});
47. How do you implement biometric authentication?
import * as LocalAuthentication from 'expo-local-authentication';
async function authenticateWithBiometrics() {
const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!hasHardware || !isEnrolled) {
return fallbackToPIN();
}
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Confirm your identity',
fallbackLabel: 'Use PIN',
disableDeviceFallback: false,
});
if (result.success) {
unlockApp();
} else {
Alert.alert('Authentication failed');
}
}
48. What are common React Native anti-patterns?
| Anti-pattern | Problem | Fix |
|---|---|---|
| Inline styles in render | New object on every render | Use StyleSheet.create() |
Anonymous functions in renderItem |
Breaks React.memo |
Use useCallback |
ScrollView for long lists |
Renders all items at once | Use FlatList |
useState for server state |
Stale data, no caching | Use TanStack Query |
Secrets in .env / JS bundle |
Extractable from APK | Use backend proxy |
Missing keyExtractor |
Unstable reconciliation | Always provide unique key |
async in useEffect directly |
Memory leaks, race conditions | Wrap in inner async fn with cleanup |
| Hardcoded platform strings | Breaks cross-platform | Use Platform.select |
49. React Native vs Flutter vs Native — when to choose each?
| Factor | React Native | Flutter | Native (iOS/Android) |
|---|---|---|---|
| Language | JavaScript / TypeScript | Dart | Swift / Kotlin |
| Code sharing | ~85-90% | ~85-90% | 0% |
| Performance | Near-native (JSI) | Near-native (Impeller) | Native |
| UI fidelity | Native components | Custom (pixel-perfect) | Native |
| Ecosystem | npm (vast) | pub.dev | Platform SDKs |
| Team skills | Web devs | Dart learners | Mobile specialists |
| Hot reload | Yes | Yes | Limited |
| Best for | Web teams going mobile | Pixel-perfect UI / new projects | Maximum performance + platform features |
50. How do you prepare a React Native app for production?
# 1. Enable Hermes
# android/app/build.gradle: enableHermes: true
# 2. Enable ProGuard (Android)
# android/app/build.gradle: minifyEnabled = true
# 3. Configure app signing
# android/app/build.gradle: signingConfig
# 4. Build production bundle
npx react-native bundle \
--platform android \
--dev false \
--entry-file index.js \
--bundle-output android/app/src/main/assets/index.android.bundle
# 5. Build release APK / AAB
cd android && ./gradlew bundleRelease
# 6. Build iOS release
cd ios && xcodebuild archive -scheme MyApp -configuration Release
Pre-launch checklist:
- Remove all
console.logcalls (or use a logger that strips in prod) - Disable remote debugger in production
- Test on real low-end devices
- Set up crash reporting (Sentry / Bugsnag)
- Configure app icons and splash screen
- Handle all error boundaries
Common Mistakes
| Mistake | Why it's a problem | Fix |
|---|---|---|
Not using keyExtractor |
List items remount unexpectedly | Always provide unique stable keys |
Using index as key |
Breaks reconciliation on reorder | Use item ID |
| Blocking JS thread | 60fps drops, unresponsive UI | Use InteractionManager or worklets |
Not cleaning up in useEffect |
Memory leaks, stale callbacks | Return cleanup function |
Using ScrollView for large lists |
All items rendered upfront | Use FlatList |
| Secrets in bundle | Extractable from APK/IPA | Backend proxy for sensitive calls |
| No offline handling | App crashes without internet | Check NetInfo, cache with TanStack Query |
| Missing error boundaries | Crash on any JS error | Wrap screens in ErrorBoundary |
React Native vs Flutter vs React Native Web
| Feature | React Native | Flutter | RN + React Native Web |
|---|---|---|---|
| iOS | ✅ | ✅ | ✅ |
| Android | ✅ | ✅ | ✅ |
| Web | Via RN Web | Via Flutter Web | ✅ |
| Desktop | Via Electron | ✅ (macOS/Linux/Win) | Partial |
| TV | ✅ (tvOS/Android TV) | ❌ | ❌ |
| Code sharing | Mobile ~90% | Mobile ~90% | All platforms ~75% |
FAQ
Q: Is React Native still relevant in 2025?
Yes. Meta, Shopify, Discord, and Microsoft use React Native in production. The New Architecture (JSI + Fabric) closes the performance gap with native, and Expo has significantly improved developer experience.
Q: Do I need to know iOS/Android to use React Native?
Not for day-to-day development. But understanding native concepts (lifecycle, permissions, background tasks) helps when debugging platform-specific issues or writing native modules.
Q: What's the difference between React Native CLI and Expo CLI?
React Native CLI (bare) gives full control and requires Xcode/Android Studio. Expo CLI sets up a managed project with cloud builds (EAS), pre-built native modules, and simpler workflow — recommended for most projects.
Q: How do I handle different screen sizes in React Native?
Use Dimensions.get('window'), useWindowDimensions() hook, or % units in styles. For responsive layouts, prefer Flexbox percentages over fixed pixel values.
Q: Can React Native apps access all device APIs?
Most APIs have community or Expo libraries. Very new or specialised APIs may need a custom native module. The Expo library ecosystem covers the vast majority of common needs.
Q: What is the best state management for React Native in 2025?
For server state: TanStack Query. For client state: Zustand (simple) or Redux Toolkit (complex). Avoid over-engineering — start with useState + Context and add libraries when genuinely needed.