Back to Articles
Interview PrepReact NativeInterviewJavaScript

React Native Senior Developer Interview Questions & Answers

15 min read

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.memo with custom comparison
  • Implement getItemLayout for fixed-height items
  • Set appropriate windowSize and maxToRenderPerBatch
  • Enable removeClippedSubviews on Android
  • Use useCallback for renderItem and keyExtractor

    2. 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

    @interface RNBiometricAuth : NSObject @end

    // 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.biometric

    import 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:

  • Redux: Large apps, complex state, time-travel debugging needed
  • Zustand: Medium apps, simpler API, better performance
  • Context API: Small apps, simple state sharing
  • MobX: Reactive programming style preferred, automatic UI updates

    4. Navigation Architecture

    Question: How do you implement deep linking with React Navigation?

    Answer:

    javascript
  • // App.js import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { Linking } from 'react-native';

    const 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 (

    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! 🚀