November 22, 2025
19 min read

Senior React Native Developer Interview Questions and Answers

interview
career-advice
job-search
Senior React Native Developer Interview Questions and Answers
Milad Bonakdar

Milad Bonakdar

Author

Prepare for senior React Native interviews with practical questions on architecture, FlatList performance, native modules, state management, testing, and production trade-offs.


Introduction

Senior React Native interviews usually test more than syntax. Expect questions about app architecture, performance profiling, native integration, state and data boundaries, testing strategy, release quality, and how you explain trade-offs under real product constraints.

Use this guide to practice concise, senior-level answers. Start with the direct decision you would make, then explain the reasoning, risk, and fallback. For example, do not just say that FlatList can be optimized; explain how you would profile blank areas, tune rendering batches, memoize rows, use stable keys, and change the design when fixed item heights or heavy images are the real issue.


Advanced React & Hooks (5 Questions)

1. Explain useMemo and useCallback. When should you use them?

Answer: Both hooks optimize performance by memoizing values/functions.

  • useMemo: Memoizes computed values (expensive calculations)
  • useCallback: Memoizes function references (prevents recreation)
  • When to use: Only when you have performance issues. Premature optimization can make code harder to read.
import { useMemo, useCallback, useState } from 'react';

function ExpensiveComponent({ items, onItemClick }) {
  // useMemo - memoize expensive calculation
  const sortedItems = useMemo(() => {
    console.log('Sorting items...');
    return items.sort((a, b) => a.price - b.price);
  }, [items]); // Only recalculate when items change
  
  // useCallback - memoize function
  const handleClick = useCallback((id) => {
    console.log('Item clicked:', id);
    onItemClick(id);
  }, [onItemClick]); // Only recreate when onItemClick changes
  
  return (
    <FlatList
      data={sortedItems}
      renderItem={({ item }) => (
        <ItemRow item={item} onClick={handleClick} />
      )}
    />
  );
}

// Child component with React.memo
const ItemRow = React.memo(({ item, onClick }) => {
  console.log('Rendering item:', item.id);
  return (
    <TouchableOpacity onPress={() => onClick(item.id)}>
      <Text>{item.name}</Text>
    </TouchableOpacity>
  );
});

Rarity: Very Common Difficulty: Medium


2. What is useRef and what are its use cases?

Answer: useRef creates a mutable reference that persists across renders without causing re-renders.

  • Use Cases:
    • Accessing DOM/native elements
    • Storing mutable values without triggering re-render
    • Keeping previous values
    • Storing timers/intervals
import { useRef, useEffect } from 'react';
import { TextInput, Animated } from 'react-native';

function FormComponent() {
  const inputRef = useRef(null);
  const previousValue = useRef('');
  const animatedValue = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    // Focus input on mount
    inputRef.current?.focus();
  }, []);
  
  const handleChange = (text) => {
    console.log('Previous:', previousValue.current);
    console.log('Current:', text);
    previousValue.current = text;
  };
  
  const startAnimation = () => {
    Animated.timing(animatedValue, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();
  };
  
  return (
    <View>
      <TextInput ref={inputRef} onChangeText={handleChange} />
      <Animated.View style={{ opacity: animatedValue }}>
        <Text>Animated Content</Text>
      </Animated.View>
    </View>
  );
}

// Timer example
function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    
    return () => clearInterval(intervalRef.current);
  }, []);
  
  return <Text>{count}</Text>;
}

Rarity: Common Difficulty: Medium


3. Explain Custom Hooks and when to create them.

Answer: Custom hooks extract reusable stateful logic into separate functions.

  • Benefits: Code reuse, separation of concerns, easier testing
  • Convention: Must start with "use"
// Custom hook for API fetching
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

// Custom hook for form handling
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  
  const handleChange = (name, value) => {
    setValues(prev => ({ ...prev, [name]: value }));
  };
  
  const validate = (validationRules) => {
    const newErrors = {};
    Object.keys(validationRules).forEach(field => {
      const rule = validationRules[field];
      if (rule.required && !values[field]) {
        newErrors[field] = `${field} is required`;
      }
    });
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  return { values, errors, handleChange, validate };
}

// Usage
function UserProfile() {
  const { data, loading, error } = useFetch('https://api.example.com/user');
  const { values, errors, handleChange, validate } = useForm({
    name: '',
    email: '',
  });
  
  if (loading) return <ActivityIndicator />;
  if (error) return <Text>Error: {error}</Text>;
  
  return <Text>{data.name}</Text>;
}

Rarity: Common Difficulty: Medium


4. What is React Context and when should you use it?

Answer: Context provides a way to pass data through the component tree without prop drilling.

  • Use Cases: Theme, authentication, language preferences
  • Caution: Can cause unnecessary re-renders if not used carefully
import { createContext, useContext, useState } from 'react';

// Create context
const ThemeContext = createContext();
const AuthContext = createContext();

// Theme Provider
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Auth Provider
function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  
  const login = async (credentials) => {
    const user = await loginAPI(credentials);
    setUser(user);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Custom hooks for context
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

function useAuth() {
  return useContext(AuthContext);
}

// Usage
function App() {
  return (
    <ThemeProvider>
      <AuthProvider>
        <MainApp />
      </AuthProvider>
    </ThemeProvider>
  );
}

function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  const { user } = useAuth();
  
  return (
    <TouchableOpacity
      style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}
      onPress={toggleTheme}
    >
      <Text>{user ? `Hello, ${user.name}` : 'Guest'}</Text>
    </TouchableOpacity>
  );
}

Rarity: Very Common Difficulty: Medium


5. Explain the difference between useEffect and useLayoutEffect.

Answer: Both run side effects, but at different times:

  • useEffect: Runs asynchronously after render is painted to screen
  • useLayoutEffect: Runs synchronously before paint (blocks visual updates)
  • Use useLayoutEffect when: You need to measure DOM or prevent visual flicker
import { useEffect, useLayoutEffect, useRef, useState } from 'react';

function MeasureComponent() {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);
  
  // useLayoutEffect - measure before paint
  useLayoutEffect(() => {
    if (elementRef.current) {
      const { height } = elementRef.current.measure((x, y, width, height) => {
        setHeight(height);
      });
    }
  }, []);
  
  // useEffect - after paint
  useEffect(() => {
    console.log('Component rendered');
  }, []);
  
  return (
    <View ref={elementRef}>
      <Text>Height: {height}</Text>
    </View>
  );
}

Rarity: Medium Difficulty: Hard


State Management (4 Questions)

6. Explain Redux and its core principles.

Answer: Redux is a predictable state container for JavaScript apps.

Loading diagram...
  • Core Principles:
    • Single source of truth (one store)
    • State is read-only (dispatch actions to change)
    • Changes made with pure functions (reducers)
// Action types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';

// Action creators
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { id: Date.now(), text, completed: false },
});

const toggleTodo = (id) => ({
  type: TOGGLE_TODO,
  payload: id,
});

// Reducer
const todosReducer = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, action.payload];
    case TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    default:
      return state;
  }
};

// Store
import { createStore } from 'redux';
const store = createStore(todosReducer);

// React Native component
import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();
  
  return (
    <View>
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => dispatch(toggleTodo(item.id))}>
            <Text style={{ textDecorationLine: item.completed ? 'line-through' : 'none' }}>
              {item.text}
            </Text>
          </TouchableOpacity>
        )}
      />
      <Button title="Add" onPress={() => dispatch(addTodo('New Task'))} />
    </View>
  );
}

Rarity: Very Common Difficulty: Hard


7. What is Redux Toolkit and how does it simplify Redux?

Answer: Redux Toolkit is the official recommended way to write Redux logic.

  • Benefits:
    • Less boilerplate
    • Built-in Immer for immutable updates
    • Includes Redux Thunk
    • Better TypeScript support
import { createSlice, configureStore } from '@reduxjs/toolkit';

// Slice (combines actions and reducer)
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      // Immer allows "mutating" code
      state.push({
        id: Date.now(),
        text: action.payload,
        completed: false,
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
  },
});

export const { addTodo, toggleTodo } = todosSlice.actions;

// Store
const store = configureStore({
  reducer: {
    todos: todosSlice.reducer,
  },
});

// Async thunk
import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await fetch('https://api.example.com/todos');
    return response.json();
  }
);

const todosSlice = createSlice({
  name: 'todos',
  initialState: { items: [], loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

Rarity: Common Difficulty: Medium


8. What are alternatives to Redux for state management?

Answer: Multiple state management solutions exist:

  • Context API + useReducer: Built-in, good for simple apps
  • MobX: Observable-based, less boilerplate
  • Zustand: Minimal, hooks-based
  • Recoil: Atom-based, by Facebook
  • Jotai: Primitive atoms
// Zustand example
import create from 'zustand';

const useStore = create((set) => ({
  todos: [],
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: Date.now(), text, completed: false }],
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ),
  })),
}));

// Usage
function TodoList() {
  const { todos, addTodo, toggleTodo } = useStore();
  
  return (
    <View>
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => toggleTodo(item.id)}>
            <Text>{item.text}</Text>
          </TouchableOpacity>
        )}
      />
    </View>
  );
}

Rarity: Common Difficulty: Medium


9. How do you handle side effects in Redux?

Answer: Use middleware for async operations:

  • Redux Thunk: Functions that return functions
  • Redux Saga: Generator-based, more powerful
  • Redux Observable: RxJS-based
// Redux Thunk
const fetchUser = (userId) => async (dispatch) => {
  dispatch({ type: 'FETCH_USER_REQUEST' });
  
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    const user = await response.json();
    dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (error) {
    dispatch({ type: 'FETCH_USER_FAILURE', payload: error.message });
  }
};

// Usage
dispatch(fetchUser(123));

// Redux Saga
import { call, put, takeEvery } from 'redux-saga/effects';

function* fetchUserSaga(action) {
  try {
    const user = yield call(fetch, `https://api.example.com/users/${action.payload}`);
    const data = yield call([user, 'json']);
    yield put({ type: 'FETCH_USER_SUCCESS', payload: data });
  } catch (error) {
    yield put({ type: 'FETCH_USER_FAILURE', payload: error.message });
  }
}

function* watchFetchUser() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUserSaga);
}

Rarity: Common Difficulty: Hard


Performance Optimization (5 Questions)

10. How do you optimize FlatList performance?

Answer: Multiple strategies improve FlatList scrolling, but a senior answer starts with profiling the symptom: blank areas, slow taps, memory pressure, expensive row renders, or network pagination. Tune the list after you know which problem you are solving.

  1. Use keyExtractor: Provide stable unique keys
  2. getItemLayout: Skip measurement when item height or width is predictable
  3. Tune render batches: Balance initialNumToRender, maxToRenderPerBatch, updateCellsBatchingPeriod, and windowSize
  4. Keep rows light: Move heavy logic out of row components and resize or cache images
  5. Memoize row components and renderItem: Use React.memo and useCallback when prop references are stable
  6. Use pagination carefully: Avoid blocking interactions while appending data
  7. Validate on real devices: Simulator performance can hide memory and scroll issues
import React, { memo, useCallback } from 'react';

const ITEM_HEIGHT = 80;

// Memoized item component
const ListItem = memo(({ item, onPress }) => {
  return (
    <TouchableOpacity onPress={() => onPress(item.id)} style={{ height: ITEM_HEIGHT }}>
      <Text>{item.name}</Text>
    </TouchableOpacity>
  );
});

function OptimizedList({ data }) {
  const handlePress = useCallback((id) => {
    console.log('Pressed:', id);
  }, []);
  
  const renderItem = useCallback(({ item }) => (
    <ListItem item={item} onPress={handlePress} />
  ), [handlePress]);
  
  const keyExtractor = useCallback((item) => item.id.toString(), []);
  
  const getItemLayout = useCallback((data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  }), []);
  
  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      getItemLayout={getItemLayout}
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={5}
    />
  );
}

Rarity: Very Common Difficulty: Medium


11. What is the React Native bridge and how does it affect performance?

Answer: The legacy bridge is the communication layer between JavaScript and native code. Older React Native apps send serialized asynchronous messages across that boundary, so frequent JavaScript/native communication can hurt animation, scrolling, and startup work.

  • How it works in the legacy architecture:
    • JavaScript runs separately from native UI and platform code
    • Native modules run on native threads
    • Data crossing the bridge must be serialized
  • Performance impact:
    • Frequent small calls can become a bottleneck
    • Large payloads add serialization cost
    • UI work can feel delayed when JS is busy
  • Senior-level mitigation:
    • Reduce bridge crossings and batch native work
    • Use native-driven animations or Reanimated for gesture-heavy flows
    • Profile before rewriting code
    • For newer apps, understand how JSI, Fabric, and TurboModules reduce bridge-era limits while still requiring library compatibility checks
// Bad - frequent bridge crossings
const BadAnimation = () => {
  const [position, setPosition] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setPosition(p => p + 1); // Bridge crossing every frame!
    }, 16);
    return () => clearInterval(interval);
  }, []);
  
  return <View style={{ transform: [{ translateX: position }] }} />;
};

// Good - native animation (no bridge)
const GoodAnimation = () => {
  const translateX = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.timing(translateX, {
      toValue: 100,
      duration: 1000,
      useNativeDriver: true, // Runs on native thread!
    }).start();
  }, []);
  
  return <Animated.View style={{ transform: [{ translateX }] }} />;
};

Rarity: Common Difficulty: Hard


12. How do you prevent unnecessary re-renders?

Answer: Multiple techniques prevent wasted renders:

  1. React.memo: Memoize components
  2. useMemo/useCallback: Memoize values/functions
  3. Proper key props: Help React identify changes
  4. Avoid inline objects/arrays: Create new references
  5. Split components: Smaller, focused components
// Bad - creates new object every render
function BadComponent() {
  return <ChildComponent style={{ margin: 10 }} />; // New object!
}

// Good - stable reference
const styles = StyleSheet.create({
  container: { margin: 10 },
});

function GoodComponent() {
  return <ChildComponent style={styles.container} />;
}

// React.memo with custom comparison
const ExpensiveComponent = React.memo(
  ({ data, onPress }) => {
    console.log('Rendering ExpensiveComponent');
    return (
      <View>
        <Text>{data.name}</Text>
        <Button onPress={onPress} />
      </View>
    );
  },
  (prevProps, nextProps) => {
    // Custom comparison - only re-render if data.id changed
    return prevProps.data.id === nextProps.data.id;
  }
);

// Split components to isolate re-renders
function ParentComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  return (
    <View>
      {/* Only re-renders when count changes */}
      <CountDisplay count={count} />
      
      {/* Only re-renders when text changes */}
      <TextDisplay text={text} />
      
      <Button onPress={() => setCount(c => c + 1)} />
    </View>
  );
}

const CountDisplay = React.memo(({ count }) => <Text>{count}</Text>);
const TextDisplay = React.memo(({ text }) => <Text>{text}</Text>);

Rarity: Very Common Difficulty: Medium


13. How do you optimize images in React Native?

Answer: Image optimization is crucial for performance:

  1. Resize images: Use appropriate dimensions
  2. Cache images: Use libraries like react-native-fast-image
  3. Lazy loading: Load images on demand
  4. Progressive loading: Show placeholder first
  5. Use WebP format: Better compression
import FastImage from 'react-native-fast-image';

function OptimizedImage({ uri }) {
  return (
    <FastImage
      source={{
        uri,
        priority: FastImage.priority.normal,
        cache: FastImage.cacheControl.immutable,
      }}
      resizeMode={FastImage.resizeMode.cover}
      style={{ width: 200, height: 200 }}
    />
  );
}

// Progressive image loading
function ProgressiveImage({ thumbnailUri, fullUri }) {
  const [imageLoaded, setImageLoaded] = useState(false);
  
  return (
    <View>
      <Image
        source={{ uri: thumbnailUri }}
        style={styles.image}
        blurRadius={imageLoaded ? 0 : 5}
      />
      <Image
        source={{ uri: fullUri }}
        style={[styles.image, { opacity: imageLoaded ? 1 : 0 }]}
        onLoad={() => setImageLoaded(true)}
      />
    </View>
  );
}

// Image prefetching
import { Image } from 'react-native';

const prefetchImages = async (urls) => {
  const promises = urls.map(url => Image.prefetch(url));
  await Promise.all(promises);
};

Rarity: Common Difficulty: Medium


14. What tools do you use for performance profiling?

Answer: Multiple tools help identify performance issues:

  • React DevTools Profiler: Component render times
  • Flipper: Debugging and profiling tool
  • Performance Monitor: Built-in FPS monitor
  • Systrace: Android performance tracing
  • Instruments: iOS performance profiling
// Enable performance monitor in dev
import { LogBox } from 'react-native';

if (__DEV__) {
  // Show performance monitor
  require('react-native').unstable_enableLogBox();
}

// Measure component render time
import { Profiler } from 'react';

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
}

// Custom performance tracking
const measurePerformance = (name, fn) => {
  const start = performance.now();
  const result = fn();
  const end = performance.now();
  console.log(`${name} took ${end - start}ms`);
  return result;
};

Rarity: Common Difficulty: Medium


Native Modules & Platform-Specific (4 Questions)

15. How do you create a Native Module in React Native?

Answer: Native modules allow you to use platform-specific code.

// iOS (Objective-C) - CalendarManager.m
#import "CalendarManager.h"
#import <React/RCTLog.h>

@implementation CalendarManager

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
  RCTLogInfo(@"Creating event %@ at %@", name, location);
}

@end

// Android (Java) - CalendarModule.java
package com.myapp;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class CalendarModule extends ReactContextBaseJavaModule {
  CalendarModule(ReactApplicationContext context) {
    super(context);
  }

  @Override
  public String getName() {
    return "CalendarManager";
  }

  @ReactMethod
  public void addEvent(String name, String location) {
    Log.d("CalendarModule", "Creating event " + name + " at " + location);
  }
}

// JavaScript usage
import { NativeModules } from 'react-native';

const { CalendarManager } = NativeModules;

function MyComponent() {
  const createEvent = () => {
    CalendarManager.addEvent('Meeting', 'Office');
  };
  
  return <Button title="Create Event" onPress={createEvent} />;
}

Rarity: Medium Difficulty: Hard


16. How do you handle platform-specific code?

Answer: Multiple approaches for platform-specific code:

  1. Platform module: Check platform at runtime
  2. Platform-specific files: .ios.js and .android.js
  3. Platform.select: Select values based on platform
import { Platform } from 'react-native';

// Platform module
const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.OS === 'ios' ? 20 : 0,
  },
});

// Platform.select
const styles = StyleSheet.create({
  container: {
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.3,
      },
      android: {
        elevation: 4,
      },
    }),
  },
});

// Platform-specific files
// Button.ios.js
export default function Button() {
  return <Text>iOS Button</Text>;
}

// Button.android.js
export default function Button() {
  return <Text>Android Button</Text>;
}

// Usage - automatically picks correct file
import Button from './Button';

// Platform version check
if (Platform.Version >= 21) {
  // Android API 21+
}

Rarity: Very Common Difficulty: Easy


17. What is the New Architecture (Fabric and TurboModules)?

Answer: The New Architecture is React Native's modern runtime and rendering model. It combines JSI for direct JavaScript/native interaction, Fabric as the renderer, and TurboModules for typed native modules. A senior candidate should explain both the upside and the migration risk.

  • Fabric: New rendering system
    • Better interoperability with native views
    • More synchronous layout capabilities
    • Designed for modern React features
  • TurboModules: New native module system
    • Lazy loading
    • Type-safe specs through codegen
    • Direct JSI-based communication instead of bridge serialization

Interview framing:

  • New apps should evaluate the New Architecture early
  • Existing apps need dependency, build, and rollout checks before migration
  • Performance gains depend on the app bottlenecks; do not promise automatic speedups
  • Native module ownership, CI coverage, and crash monitoring matter as much as enabling a flag

Rarity: Medium Difficulty: Hard


18. How do you handle deep linking in React Native?

Answer: Deep linking allows opening specific screens from URLs.

import { Linking } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';

// Configure deep linking
const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Home: 'home',
      Profile: 'profile/:id',
      Settings: 'settings',
    },
  },
};

function App() {
  return (
    <NavigationContainer linking={linking}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

// Handle incoming links
useEffect(() => {
  const handleUrl = ({ url }) => {
    console.log('Opened with URL:', url);
    // myapp://profile/123
  };
  
  // App opened from link
  Linking.getInitialURL().then(url => {
    if (url) handleUrl({ url });
  });
  
  // App already open, new link
  const subscription = Linking.addEventListener('url', handleUrl);
  
  return () => subscription.remove();
}, []);

// Open URL programmatically
const openURL = async (url) => {
  const supported = await Linking.canOpenURL(url);
  if (supported) {
    await Linking.openURL(url);
  }
};

Rarity: Common Difficulty: Medium


Testing (3 Questions)

19. How do you test React Native components?

Answer: Use testing libraries like Jest and React Native Testing Library.

import { render, fireEvent, waitFor } from '@testing-library/react-native';
import Counter from './Counter';

describe('Counter', () => {
  it('renders initial count', () => {
    const { getByText } = render(<Counter />);
    expect(getByText('Count: 0')).toBeTruthy();
  });
  
  it('increments count when button pressed', () => {
    const { getByText, getByTestId } = render(<Counter />);
    const button = getByTestId('increment-button');
    
    fireEvent.press(button);
    
    expect(getByText('Count: 1')).toBeTruthy();
  });
  
  it('fetches user data', async () => {
    const mockFetch = jest.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve({ name: 'John' }),
      })
    );
    global.fetch = mockFetch;
    
    const { getByText } = render(<UserProfile userId={1} />);
    
    await waitFor(() => {
      expect(getByText('John')).toBeTruthy();
    });
    
    expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/users/1');
  });
});

// Component with testID
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <View>
      <Text>Count: {count}</Text>
      <Button
        testID="increment-button"
        title="Increment"
        onPress={() => setCount(c => c + 1)}
      />
    </View>
  );
}

Rarity: Common Difficulty: Medium


20. How do you test Redux logic?

Answer: Test reducers, actions, and connected components separately.

import todosReducer, { addTodo, toggleTodo } from './todosSlice';

describe('todos reducer', () => {
  it('should handle initial state', () => {
    expect(todosReducer(undefined, { type: 'unknown' })).toEqual([]);
  });
  
  it('should handle addTodo', () => {
    const actual = todosReducer([], addTodo('Learn Redux'));
    expect(actual[0].text).toEqual('Learn Redux');
    expect(actual[0].completed).toEqual(false);
  });
  
  it('should handle toggleTodo', () => {
    const previousState = [{ id: 1, text: 'Test', completed: false }];
    const actual = todosReducer(previousState, toggleTodo(1));
    expect(actual[0].completed).toEqual(true);
  });
});

// Testing connected component
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';

function renderWithRedux(component, initialState) {
  const store = configureStore({
    reducer: { todos: todosReducer },
    preloadedState: initialState,
  });
  
  return {
    ...render(<Provider store={store}>{component}</Provider>),
    store,
  };
}

test('displays todos from store', () => {
  const { getByText } = renderWithRedux(<TodoList />, {
    todos: [{ id: 1, text: 'Test Todo', completed: false }],
  });
  
  expect(getByText('Test Todo')).toBeTruthy();
});

Rarity: Common Difficulty: Medium


21. What is E2E testing and which tools do you use?

Answer: End-to-end testing simulates real user interactions.

  • Tools:
    • Detox: Popular for React Native
    • Appium: Cross-platform
    • Maestro: Newer, simpler
// Detox test
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });
  
  it('should login successfully', async () => {
    await element(by.id('email-input')).typeText('[email protected]');
    await element(by.id('password-input')).typeText('password123');
    await element(by.id('login-button')).tap();
    
    await waitFor(element(by.text('Welcome')))
      .toBeVisible()
      .withTimeout(5000);
  });
  
  it('should show error for invalid credentials', async () => {
    await element(by.id('email-input')).typeText('[email protected]');
    await element(by.id('password-input')).typeText('wrong');
    await element(by.id('login-button')).tap();
    
    await expect(element(by.text('Invalid credentials'))).toBeVisible();
  });
});

Rarity: Medium Difficulty: Medium


Newsletter subscription

Weekly career tips that actually work

Get the latest insights delivered straight to your inbox

Your Next Interview is Just One Resume Away

Create a professional, optimized resume in minutes. No design skills needed—just proven results.

Create my resume

Share this post

Make Your 6 Seconds Count

Recruiters scan resumes for an average of only 6 to 7 seconds. Our proven templates are designed to capture attention instantly and keep them reading.