React Native Senior Developer Interview Questions & Answers
React Native Senior Developer Interview Questions & Answers
This comprehensive guide covers essential topics for senior React Native developer interviews, including performance optimization, native modules integration, and cross-platform architecture.
1. Performance Optimization
Question: How do you optimize FlatList performance for large datasets?
Answer: FlatList performance is critical for smooth scrolling. Here are the key optimization techniques:
javascript
import React, { useCallback, useMemo } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';const OptimizedList = ({ data }) => {
// Memoize renderItem to prevent unnecessary re-renders
const renderItem = useCallback(({ item }) => (
), []);
// Key extractor for efficient updates
const keyExtractor = useCallback((item) => item.id.toString(), []);
// Get item layout for better scroll performance
const getItemLayout = useCallback((data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}), []);
return (
);
};
// Memoized list item
const ListItem = React.memo(({ item }) => {
return (
{item.title}
{item.subtitle}
);
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.item.id === nextProps.item.id &&
prevProps.item.title === nextProps.item.title;
});
Key Techniques:
- Use
React.memowith custom comparison - Implement
getItemLayoutfor fixed-height items
- Set appropriate
windowSizeandmaxToRenderPerBatch - Enable
removeClippedSubviewson Android
- Use
useCallbackforrenderItemandkeyExtractor2. Native Modules Integration
Question: How do you create and use a native module in React Native?
Answer:
iOS (Objective-C/Swift):
objective-c
// RNBiometricAuth.h
#import - Redux: Large apps, complex state, time-travel debugging needed
@interface RNBiometricAuth : NSObject
// RNBiometricAuth.m
#import "RNBiometricAuth.h"
#import
@implementation RNBiometricAuth
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(authenticate:(NSString *)reason resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { LAContext *context = [[LAContext alloc] init]; NSError *error = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:reason reply:^(BOOL success, NSError *error) { if (success) { resolve(@{@"success": @YES}); } else { reject(@"AUTH_FAILED", @"Authentication failed", error); } }]; } else { reject(@"NOT_AVAILABLE", @"Biometric not available", error); } }
@end
Android (Kotlin):
kotlin
// BiometricAuthModule.kt
package com.myapp.biometricimport com.facebook.react.bridge.*
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
class BiometricAuthModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName() = "RNBiometricAuth"
@ReactMethod
fun authenticate(reason: String, promise: Promise) {
val activity = currentActivity as? FragmentActivity
if (activity == null) {
promise.reject("NO_ACTIVITY", "Activity not available")
return
}
val executor = ContextCompat.getMainExecutor(activity)
val biometricPrompt = BiometricPrompt(activity, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
val map = WritableNativeMap()
map.putBoolean("success", true)
promise.resolve(map)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
promise.reject("AUTH_ERROR", errString.toString())
}
override fun onAuthenticationFailed() {
promise.reject("AUTH_FAILED", "Authentication failed")
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Biometric Authentication")
.setSubtitle(reason)
.setNegativeButtonText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo)
}
}
JavaScript Usage:
javascript
import { NativeModules } from 'react-native';const { RNBiometricAuth } = NativeModules;
export const useBiometric = () => {
const authenticate = async (reason) => {
try {
const result = await RNBiometricAuth.authenticate(reason);
return result.success;
} catch (error) {
console.error('Biometric authentication failed:', error);
return false;
}
};
return { authenticate };
};
3. State Management
Question: Compare Redux, MobX, and Zustand. When would you use each?
Answer:
Redux with Redux Toolkit:
javascript
// store/slices/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId, { rejectWithValue }) => {
try {
const response = await fetch(/api/users/${userId});
return await response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
loading: false,
error: null,
},
reducers: {
updateUser: (state, action) => {
state.data = { ...state.data, ...action.payload };
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
},
});
export const { updateUser } = userSlice.actions;
export default userSlice.reducer;
Zustand (Lightweight Alternative):
javascript
import create from 'zustand';
import { devtools, persist } from 'zustand/middleware';const useStore = create(
devtools(
persist(
(set, get) => ({
user: null,
loading: false,
error: null,
fetchUser: async (userId) => {
set({ loading: true });
try {
const response = await fetch(/api/users/${userId});
const data = await response.json();
set({ user: data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
updateUser: (updates) => {
set((state) => ({
user: { ...state.user, ...updates }
}));
},
clearUser: () => set({ user: null, error: null }),
}),
{ name: 'user-storage' }
)
)
);
// Usage in component
const UserProfile = () => {
const { user, loading, fetchUser } = useStore();
useEffect(() => {
fetchUser('123');
}, []);
if (loading) return ;
return ;
};
When to Use:
- Zustand: Medium apps, simpler API, better performance
- Context API: Small apps, simple state sharing
4. Navigation Architecture
Question: How do you implement deep linking with React Navigation?
Answer:
javascriptconst Stack = createNativeStackNavigator();
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: '',
Profile: {
path: 'profile/:userId',
parse: {
userId: (userId) => user-${userId},
},
stringify: {
userId: (userId) => userId.replace('user-', ''),
},
},
Article: {
path: 'article/:id',
exact: true,
},
Settings: 'settings',
},
},
// Handle universal links
async getInitialURL() {
const url = await Linking.getInitialURL();
if (url != null) {
return url;
}
// Handle notifications
const notificationData = await getInitialNotification();
return notificationData?.deepLink;
},
subscribe(listener) {
const onReceiveURL = ({ url }) => listener(url);
// Listen to incoming links Linking.addEventListener('url', onReceiveURL);
// Listen to push notifications const unsubscribe = messaging().onNotificationOpenedApp(remoteMessage => { const url = remoteMessage.data?.deepLink; if (url) { listener(url); } });
return () => { Linking.removeEventListener('url', onReceiveURL); unsubscribe(); }; }, };
function App() {
return (
5. Testing Strategies
Question: How do you write effective tests for React Native components?
Answer:
javascript
// UserProfile.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import UserProfile from './UserProfile';// Setup mock server
const server = setupServer(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(
ctx.json({
id: req.params.id,
name: 'John Doe',
email: 'john@example.com',
})
);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('UserProfile', () => {
it('renders user data after loading', async () => {
const { getByText, getByTestId } = render(
);
// Check loading state
expect(getByTestId('loading-spinner')).toBeTruthy();
// Wait for data to load
await waitFor(() => {
expect(getByText('John Doe')).toBeTruthy();
expect(getByText('john@example.com')).toBeTruthy();
});
});
it('handles edit button press', async () => {
const onEdit = jest.fn();
const { getByText } = render(
);
await waitFor(() => {
expect(getByText('John Doe')).toBeTruthy();
});
fireEvent.press(getByText('Edit'));
expect(onEdit).toHaveBeenCalledWith({
id: '123',
name: 'John Doe',
email: 'john@example.com',
});
});
it('displays error when fetch fails', async () => {
server.use(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(ctx.status(500));
})
);
const { getByText } = render( );
await waitFor(() => {
expect(getByText(/error loading user/i)).toBeTruthy();
});
});
});
// Snapshot testing
it('matches snapshot', () => {
const tree = renderer.create( ).toJSON();
expect(tree).toMatchSnapshot();
});
6. Code Splitting & Lazy Loading
Question: How do you implement code splitting in React Native?
Answer:
javascript
// Dynamic imports with React.lazy
import React, { Suspense, lazy } from 'react';
import { View, ActivityIndicator } from 'react-native';// Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'));
const VideoPlayer = lazy(() => import('./VideoPlayer'));
// Loading fallback
const LoadingFallback = () => (
);
const DashboardScreen = () => {
const [showChart, setShowChart] = useState(false);
return (
{showChart && (
}>
)}
);
};
// Bundle splitting with Metro
// metro.config.js
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: true,
inlineRequires: true, // Enable inline requires for better performance
},
}),
},
};
7. Memory Management
Question: How do you prevent memory leaks in React Native?
Answer:
javascript
import { useEffect, useRef, useCallback } from 'react';const UserComponent = () => {
const isMounted = useRef(true);
const subscriptions = useRef([]);
useEffect(() => {
// Track component mount status
isMounted.current = true;
// Cleanup function
return () => {
isMounted.current = false;
// Cancel all subscriptions
subscriptions.current.forEach(sub => sub.remove());
subscriptions.current = [];
};
}, []);
const fetchData = useCallback(async () => {
try {
const response = await fetch('/api/data');
const data = await response.json();
// Only update state if component is still mounted
if (isMounted.current) {
setData(data);
}
} catch (error) {
if (isMounted.current) {
setError(error);
}
}
}, []);
useEffect(() => {
// Event listeners should be cleaned up
const subscription = EventEmitter.addListener('dataUpdated', (data) => {
if (isMounted.current) {
handleDataUpdate(data);
}
});
subscriptions.current.push(subscription);
return () => {
subscription.remove();
};
}, []);
// Use AbortController for fetch cancellation
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(response => response.json())
.then(data => {
if (isMounted.current) {
setData(data);
}
})
.catch(error => {
if (error.name !== 'AbortError' && isMounted.current) {
setError(error);
}
});
return () => {
controller.abort();
};
}, []);
return ... ;
};
Best Practices Summary
1. Performance: Memoize components, use proper FlatList optimization 2. Native Modules: Understand platform-specific implementations 3. State Management: Choose based on app complexity 4. Navigation: Implement proper deep linking and error boundaries 5. Testing: Write integration tests, use mock servers 6. Code Splitting: Lazy load heavy components 7. Memory: Clean up subscriptions, cancel async operations
Conclusion
Senior React Native development requires deep understanding of performance optimization, native platform integration, and cross-platform architecture. Master these concepts and you'll be well-prepared for senior-level interviews.
Keep building, keep learning! 🚀