React Native
Frontend System Design

Complete interview preparation β€” architecture, performance, state management, and real-world design problems with detailed explanations.

React Native React Performance Architecture

πŸ“š Table of Contents

πŸ›οΈ App Architecture

How you structure your React Native app is the most foundational system design question.

Q01
How would you design the folder structure and architecture for a large-scale React Native app?
Architecture React Native
β–Ό

The Core Idea

Large RN apps should follow a feature-based (vertical slice) architecture, not a layer-based one. Group by feature, not by type. Each feature is self-contained β€” it owns its components, hooks, store slices, types, and tests.

Recommended Structure

src/
β”œβ”€β”€ app/               # App entry, providers, global setup
β”‚   β”œβ”€β”€ App.tsx
β”‚   β”œβ”€β”€ providers/     # Redux, Theme, Navigation providers
β”‚   └── store/         # Root store configuration
β”‚
β”œβ”€β”€ features/          # βœ… Feature-based vertical slices
β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ screens/
β”‚   β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”œβ”€β”€ store/     # authSlice.ts
β”‚   β”‚   β”œβ”€β”€ api/       # authApi.ts (RTK Query / axios)
β”‚   β”‚   β”œβ”€β”€ types/
β”‚   β”‚   └── index.ts   # Public API of the feature
β”‚   β”œβ”€β”€ feed/
β”‚   β”œβ”€β”€ profile/
β”‚   └── chat/
β”‚
β”œβ”€β”€ shared/            # Truly shared, no feature dependency
β”‚   β”œβ”€β”€ components/    # Button, Modal, Avatar…
β”‚   β”œβ”€β”€ hooks/         # useDebounce, useTheme…
β”‚   β”œβ”€β”€ utils/         # formatDate, validators…
β”‚   β”œβ”€β”€ constants/
β”‚   └── theme/
β”‚
β”œβ”€β”€ navigation/        # All navigation config
β”‚   β”œβ”€β”€ RootNavigator.tsx
β”‚   β”œβ”€β”€ AuthNavigator.tsx
β”‚   └── MainTabNavigator.tsx
β”‚
└── services/          # External: analytics, storage, push
    β”œβ”€β”€ analytics.ts
    β”œβ”€β”€ storage.ts
    └── notifications.ts

Key Principles

  • Feature boundaries: Features can import from shared/ but never from each other directly β€” use events or the root store.
  • Barrel exports: Each feature has an index.ts that defines its public API. Other code only imports from that barrel.
  • Co-location: Keep tests, types, and styles next to the code they describe.
  • Absolute imports: Configure tsconfig.json path aliases like @features/auth to avoid ../../../ hell.
πŸ’‘ Interview Signal

Mention scalability boundaries β€” when a new dev joins, they should be able to work on the "payments" feature without understanding the entire codebase. Feature isolation achieves that.

Q02
Explain the React Native bridge architecture and how the new architecture (JSI/Fabric) improves it.
React Native Performance
β–Ό

Old Architecture (The Bridge)

JS Thread ──JSON serialized──▢ Native Bridge ──▢ Native Thread (React, business logic) (async, batched) (UI, device APIs) Problems: - Async serialization adds latency (~1-5ms per call) - Data must be JSON-serializable (no direct object sharing) - Bridge is a bottleneck β€” all JS↔Native goes through it - Can't share memory between threads

New Architecture (JSI + Fabric + TurboModules)

JS Thread ──JSI C++ Host Objects──▢ Native Code (synchronous, direct calls, shared memory) JSI = JavaScript Interface β€” C++ layer, no JSON serialization Fabric = New UI renderer β€” concurrent rendering, priority queues TurboModules = Lazy-loaded native modules (only loads what you use)

Practical Improvements

Aspect Old (Bridge) New (JSI)
Communication Async JSON serialization Sync C++ direct calls
Memory Copied across threads Shared memory possible
Startup All modules loaded Lazy-loaded TurboModules
Animations Can drop frames Native thread animations
TypeScript types Manual / codegen Auto-generated from C++ spec
Reanimated 3 / Gesture Handler 2 take advantage of JSI β€” their worklets run directly on the UI thread in C++, enabling 60/120fps animations with zero bridge overhead.
Q03
When would you choose React Native vs Flutter vs native iOS/Android?
Architecture React Native
β–Ό
Factor React Native Flutter Native
Team expertise JS/React devs Dart devs Swift/Kotlin devs
Code sharing ~70-90% ~80-95% 0%
Native look/feel Yes (native components) Custom (canvas-drawn) Perfect
Performance ceiling High (JSI) Very high (Skia) Maximum
Ecosystem Huge (npm) Growing Platform-specific
Web sharing React Native Web Flutter Web (beta) No
Hot reload Yes Yes Limited

Choose React Native when:

  • Your team is JS/React-heavy and you want to share logic with a web app
  • You need platform-native UI components (accessibility, OS updates for free)
  • You're integrating a large existing native codebase incrementally
  • Time-to-market is critical and you have one team

Choose Native when:

  • Maximum graphics performance (games, AR, complex camera processing)
  • Heavy use of cutting-edge platform APIs before they have RN wrappers
  • Apps where platform-specific UX is a competitive differentiator (e.g. banking)

πŸ—‚οΈ State Management

One of the most common system design topics β€” choosing and architecting state correctly.

Q04
Design a state management strategy for a large React Native social app (like Instagram).
State Architecture
β–Ό

State Classification First

Before picking a library, classify your state into 4 categories:

Type Examples Solution
Server State Feed posts, user profiles, comments React Query / RTK Query
Global UI State Auth session, theme, notifications Redux Toolkit / Zustand
Local UI State Modal open, input value, tab index useState / useReducer
URL/Navigation State Current screen, deep link params React Navigation

Recommended Stack

// 1. Server state β€” React Query handles caching, refetch, pagination
const { data: feed, isLoading } = useQuery({
  queryKey: ['feed', userId],
  queryFn: () => fetchFeed(userId),
  staleTime: 60_000,        // 1 minute fresh
  cacheTime: 5 * 60_000,    // 5 minutes in cache
});

// 2. Global UI state β€” Zustand (lightweight, no boilerplate)
const useAuthStore = create((set) => ({
  user: null,
  token: null,
  login: (user, token) => set({ user, token }),
  logout: () => set({ user: null, token: null }),
}));

// 3. Local state β€” just useState, no global store needed
const [isLiked, setIsLiked] = useState(false);

Key Design Decisions

  • Don't put server data in Redux β€” it creates a second cache that conflicts with your API cache. Use React Query for all server-sourced data.
  • Optimistic updates: For likes/follows, update local state immediately, then revert on API failure for instant UX.
  • Normalized cache: If using Redux, use createEntityAdapter to normalize data β€” a post should live in one place even if referenced from feed, profile, and search.
  • Persistence: Use redux-persist or Zustand's persist middleware for auth tokens and offline support.
πŸ’‘ Interview Signal

Interviewers love when you distinguish server state vs. client state. Many candidates put everything in Redux β€” showing you understand this distinction shows senior thinking.

Q05
Redux vs Zustand vs Context API β€” when to use each in React Native?
State Performance
β–Ό
Library Best For Avoid When Bundle Size
Context API Theme, locale, auth (low-frequency updates) High-frequency updates (causes full re-renders) 0kb (built-in)
Zustand Medium apps, simple global state, fast setup Very complex state with many transitions ~1.5kb
Redux Toolkit Enterprise apps, complex state logic, time-travel debugging Small/MVP apps β€” overkill ~12kb
Jotai/Recoil Atomic state, fine-grained subscriptions Teams unfamiliar with atom model ~3kb

Context API Performance Problem

// ❌ Bad: ALL consumers re-render when ANY value changes
const AppContext = createContext();
function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('dark');
  const [cart, setCart] = useState([]);
  // Every Cart update re-renders ALL context consumers
  return <AppContext.Provider value={{ user, theme, cart }}>
    {children}
  </AppContext.Provider>;
}

// βœ… Good: Split contexts or use Zustand selectors
const useCartStore = create((set) => ({ cart: [] }));
const CartBadge = () => {
  // Only re-renders when cart.length changes
  const count = useCartStore(state => state.cart.length);
  return <Text>{count}</Text>;
};

⚑ Performance Optimization

Performance is the #1 technical differentiator between junior and senior RN engineers.

Q06
Your FlatList with 10,000 items is janky and slow. How do you fix it?
Performance React Native
β–Ό

Diagnosis First

Before optimizing, profile with Flipper's React DevTools or the RN Performance Monitor to identify what is slow β€” JS thread, UI thread, or renders.

Solution: Layered Optimization Approach

Use keyExtractor correctly β€” stable, unique keys prevent full re-renders on data changes. keyExtractor={(item) => item.id}
Memoize the renderItem β€” wrap the item component in React.memo and pass a memoized renderItem with useCallback.
Set getItemLayout β€” if items have fixed height, this lets FlatList skip measurement entirely, a massive perf win.
Tune windowSize, initialNumToRender β€” don't render more than you need.
Remove anonymous functions from JSX β€” they create new references every render, killing memo.
FlashList instead of FlatList β€” Shopify's FlashList recycles item components (like Android RecyclerView), 5-10x faster.
// βœ… Optimized FlatList pattern
const PostItem = React.memo(({ item, onLike }) => (
  <View>
    <Text>{item.title}</Text>
    <TouchableOpacity onPress={() => onLike(item.id)}>
      <Text>Like</Text>
    </TouchableOpacity>
  </View>
));

function Feed() {
  const handleLike = useCallback((id) => {
    // stable reference, won't cause re-renders
    likePost(id);
  }, []);

  const renderItem = useCallback(({ item }) => (
    <PostItem item={item} onLike={handleLike} />
  ), [handleLike]);

  return <FlashList
    data={posts}
    renderItem={renderItem}
    estimatedItemSize={200}
    keyExtractor={(item) => item.id}
    getItemLayout={(_, index) => ({
      length: 200, offset: 200 * index, index
    })}
  />;
}
FlashList vs FlatList: FlashList (Shopify) recycles view instances like RecyclerView. It can render 10,000 items at 60fps where FlatList struggles at 1,000. Use it for any list over 50-100 items.
Q07
How do you implement smooth 60fps animations in React Native without dropping frames?
Performance React Native
β–Ό

The Core Problem

Standard JS animations run on the JS thread. If the JS thread is busy (re-renders, API calls), animation frames get dropped. Solution: move animations to the UI thread.

Animation Libraries (Ranked by Performance)

Library Runs On Use Case
Reanimated 3 (worklets) UI Thread (JS engine on UI) Complex gestures, physics, shared values
Animated API (useNativeDriver) UI Thread Simple transforms, opacity, translate
Animated API (no native driver) JS Thread ⚠️ Layout animations, color (limited)
Lottie Native thread After Effects JSON animations
CSS transitions (web) GPU Web target only
// βœ… Reanimated 3 β€” runs 100% on UI thread
import Animated, { useSharedValue, useAnimatedStyle,
  withSpring, withTiming, runOnJS } from 'react-native-reanimated';

function LikeButton() {
  const scale = useSharedValue(1);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }]
  }));

  const handlePress = () => {
    scale.value = withSpring(1.3, {}, () => {
      scale.value = withSpring(1);  // bounce back
      runOnJS(onLike)();  // call JS callback from UI thread
    });
  };

  return <Animated.View style={animatedStyle}>
    <TouchableOpacity onPress={handlePress}>
      <Text>❀️</Text>
    </TouchableOpacity>
  </Animated.View>;
}
Avoid: Setting useNativeDriver: false unless animating layout properties. Always use useNativeDriver: true for transform/opacity to keep animations on the UI thread.
Q08
How do you reduce app startup time and bundle size in React Native?
Performance React Native
β–Ό

Startup Time Optimizations

  • Hermes engine: Enable Hermes (now default in RN 0.70+). It AOT-compiles JS to bytecode, reducing parse time by ~50%.
  • Lazy navigation: Only load screen modules when navigated to, not at startup.
  • Inline requires: Enable inlineRequires in Metro config β€” modules only load when first accessed.
  • Splash screen: Use react-native-bootsplash to hide white flash during JS loading.
  • RAM Bundles: Load JS modules on-demand from disk instead of loading the full bundle.

Bundle Size Reduction

  • Tree shaking: Use named imports: import { debounce } from 'lodash-es' not import _ from 'lodash'.
  • Analyze bundle: npx react-native bundle-visualizer β€” find what's eating your bundle.
  • Replace heavy libraries: date-fns over moment.js (250kb β†’ 20kb tree-shaken).
  • Image optimization: Use WebP format, resize images at upload time, never ship full-resolution images.
  • Codepush / OTA updates: Deliver JS updates without App Store review.
// metro.config.js β€” inline requires for lazy loading
module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        inlineRequires: true,  // modules load on first use
      },
    }),
  },
};
Q09
How do you prevent unnecessary re-renders in React Native?
Performance State
β–Ό

The 4 Causes of Unnecessary Re-renders

Cause Fix
New object reference on each render useMemo for objects, useCallback for functions
Component not memoized React.memo(Component)
Subscribing to entire store Use selectors: useSelector(state => state.user.name)
Context value changes on every render Memoize context value with useMemo
// ❌ Creates new array every render, kills memo
<PostList filters={['trending', 'new']} />

// βœ… Stable reference
const FILTERS = ['trending', 'new']; // outside component
<PostList filters={FILTERS} />

// βœ… useMemo for expensive computed values
const sortedPosts = useMemo(
  () => [...posts].sort((a, b) => b.likes - a.likes),
  [posts]  // only re-sort when posts array changes
);

// βœ… Correct React.memo with custom comparator
const Avatar = React.memo(({ user }) => (
  <Image source={{ uri: user.avatar }} />
), (prev, next) => prev.user.id === next.user.id);
Profiling tool: Use why-did-you-render library to print console warnings whenever a component re-renders with the same props β€” invaluable for finding unnecessary renders.

🌐 Network & API Layer

Designing a robust, resilient API client layer that handles errors, retries, and caching.

Q11
Design a robust API client layer for a React Native app. Include auth, retries, and error handling.
Network Architecture
β–Ό

Architecture: Axios + Interceptors

// api/client.ts β€” Central API client
import axios from 'axios';

const apiClient = axios.create({
  baseURL: getApiBaseUrl(),
  timeout: 15000,
  headers: { 'Content-Type': 'application/json' },
});

// ── Request interceptor: inject auth token ──
apiClient.interceptors.request.use(async (config) => {
  const token = await getAccessToken();
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// ── Response interceptor: handle 401 token refresh ──
let isRefreshing = false;
let failedQueue = [];

apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const original = error.config;

    if (error.response?.status === 401 && !original._retry) {
      if (isRefreshing) {
        // Queue requests while refreshing
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        }).then(token => {
          original.headers.Authorization = `Bearer ${token}`;
          return apiClient(original);
        });
      }

      original._retry = true;
      isRefreshing = true;

      try {
        const newToken = await refreshAccessToken();
        processQueue(null, newToken);  // retry queued requests
        original.headers.Authorization = `Bearer ${newToken}`;
        return apiClient(original);
      } catch (refreshError) {
        processQueue(refreshError, null);
        logout();  // force re-login
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
      }
    }
    return Promise.reject(error);
  }
);

Retry Logic for Network Failures

// Exponential backoff retry
import axiosRetry from 'axios-retry';

axiosRetry(apiClient, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,  // 1s, 2s, 4s
  retryCondition: (error) =>
    axiosRetry.isNetworkError(error) ||
    error.response?.status >= 500,  // retry server errors only
});
Q12
How do you implement infinite scroll and pagination in React Native?
Network Performance
β–Ό

Cursor-based vs Offset Pagination

Method Pros Cons Use When
Offset (?page=2) Simple, easy to jump to page N Drift when items insert/delete Static data, admin panels
Cursor (after: id) Consistent, no drift Can't jump to arbitrary page Feeds, social content
// React Query infinite scroll β€” clean pattern
const {
  data, fetchNextPage, hasNextPage, isFetchingNextPage
} = useInfiniteQuery({
  queryKey: ['feed'],
  queryFn: ({ pageParam = null }) =>
    fetchPosts({ cursor: pageParam, limit: 20 }),
  getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
});

// Flatten pages for FlatList
const posts = useMemo(
  () => data?.pages.flatMap(page => page.posts) ?? [],
  [data]
);

return <FlashList
  data={posts}
  renderItem={renderItem}
  estimatedItemSize={200}
  onEndReached={() => {
    if (hasNextPage && !isFetchingNextPage)
      fetchNextPage();
  }}
  onEndReachedThreshold={0.3}   // trigger 30% before end
  ListFooterComponent={() =>
    isFetchingNextPage ? <ActivityIndicator /> : null
  }
/>;

πŸ“΄ Offline First & Caching

Mobile apps must handle poor/no connectivity gracefully.

Q13
Design an offline-first architecture for a React Native app. How do you handle sync conflicts?
React Native Architecture Network
β–Ό

Offline-First Architecture

User Action β”‚ β–Ό Local DB (SQLite / MMKV / WatermelonDB) ← Single source of truth β”‚ β”œβ”€β”€ Immediately render UI from local data β”‚ β”œβ”€β”€ Queue mutation for sync ──▢ Sync Queue β”‚ β”‚ β”‚ Online? β”‚ β”‚ β”Œβ”€β”€Yes──▢ API Server β”‚ β”‚ β”‚ β”‚ β”‚ ◀── Response β”‚ β”‚ β”‚ └─────── Update local DB β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Resolve conflicts here

Implementation: WatermelonDB (Recommended)

// WatermelonDB β€” designed for offline-first RN apps
// Lazy-loads, Observable queries, sync protocol built-in

import { synchronize } from '@nozbe/watermelondb/sync';

async function syncWithServer() {
  await synchronize({
    database,
    pullChanges: async ({ lastPulledAt }) => {
      const response = await api.get(`/sync?since=${lastPulledAt}`);
      return response.data; // { changes: {...}, timestamp: ... }
    },
    pushChanges: async ({ changes }) => {
      await api.post('/sync', { changes });
    },
  });
}

Conflict Resolution Strategies

  • Last-Write-Wins (LWW): Use timestamps β€” the most recent write wins. Simple but can lose data.
  • Server-Wins: Server's version always overrides local. Safe for financial data.
  • Client-Wins: Local version overrides server. Good for user preferences.
  • Merge: Combine both changes (e.g. CRDT for collaborative text). Complex but lossless.
  • User prompt: Show the user both versions and let them choose. Last resort for important data.

Network State Detection

import NetInfo from '@react-native-community/netinfo';

function useNetworkSync() {
  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      if (state.isConnected && state.isInternetReachable) {
        syncWithServer();  // trigger sync when back online
      }
    });
    return unsubscribe;
  }, []);
}

πŸ” Security

Mobile security is different from web β€” local storage is exposed, certificates can be intercepted.

Q14
How do you securely store sensitive data (tokens, keys) in React Native?
Security React Native
β–Ό
Storage Method Security Level Use For
AsyncStorage ❌ Plain text on disk Non-sensitive user preferences only
MMKV ⚠️ Encrypted option available Fast non-sensitive data
Keychain (iOS) / Keystore (Android) βœ… OS-level encryption Auth tokens, passwords, API keys
Expo SecureStore βœ… Wraps Keychain/Keystore Expo apps β€” tokens
In-memory only βœ… Cleared on app close Highly sensitive temp data
// βœ… Correct: react-native-keychain for tokens
import * as Keychain from 'react-native-keychain';

async function saveToken(token) {
  await Keychain.setGenericPassword('auth', token, {
    accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
    securityLevel: Keychain.SECURITY_LEVEL.SECURE_SOFTWARE,
  });
}

async function getToken() {
  const credentials = await Keychain.getGenericPassword();
  return credentials ? credentials.password : null;
}

Additional Security Measures

  • Certificate pinning: Pin your API certificate to prevent MITM attacks. Use react-native-ssl-pinning.
  • Root/Jailbreak detection: Use react-native-jail-monkey to detect compromised devices.
  • Biometric auth: Use react-native-biometrics for Face ID / Fingerprint.
  • Code obfuscation: Enable Hermes + ProGuard (Android) to make reverse engineering harder.
  • Avoid logging sensitive data: Strip all console.log in production builds.
Never do this: Don't store tokens in AsyncStorage, Redux state that gets persisted to AsyncStorage, or global variables. All of these are readable on rooted/jailbroken devices.

πŸ§ͺ Testing Strategy

A testing pyramid tailored for React Native apps.

Q15
Design a complete testing strategy for a production React Native app.
Testing Architecture
β–Ό

The Testing Pyramid for React Native

E2E Tests ~10% (Detox / Maestro) Slow, expensive, high confidence Test critical user journeys only Integration Tests ~30% (React Native Testing Library) Components + hooks + state together Test user-facing behavior, not implementation Unit Tests ~60% (Jest + React Native Testing Library) Pure functions, hooks, utility logic Fast, isolated, very high coverage
// Integration test with RNTL β€” test behavior, not implementation
import { render, fireEvent, waitFor } from '@testing-library/react-native';

describe('LoginScreen', () => {
  it('shows error when login fails', async () => {
    mockApi.post('/auth/login').replyOnce(401);

    const { getByPlaceholderText, getByText } = render(<LoginScreen />);

    fireEvent.changeText(getByPlaceholderText('Email'), 'bad@email.com');
    fireEvent.changeText(getByPlaceholderText('Password'), 'wrongpass');
    fireEvent.press(getByText('Login'));

    await waitFor(() => {
      expect(getByText('Invalid credentials')).toBeTruthy();
    });
  });
});

What to Test at Each Level

  • Unit: Pure utility functions, custom hooks (renderHook), Redux reducers/selectors
  • Integration: Screens with mock API, form validation, navigation flows, state changes
  • E2E (Detox): Login β†’ Feed β†’ Like post β†’ Logout; Payment flow; Onboarding

πŸ—οΈ Real-World System Design Problems

The big system design questions β€” design an entire feature end to end.

Q16
Design a real-time chat system in React Native (like WhatsApp).
Architecture Network React Native
β–Ό

System Components

React Native App β”‚ β”œβ”€β”€ WebSocket Client (socket.io / native WS) β”‚ β”‚ Real-time: new messages, read receipts, typing β”‚ β–Ό β”‚ WebSocket Server β”‚ β”œβ”€β”€ REST API (message history, chat list) β”‚ β–Ό β”‚ API Server + DB β”‚ └── Local DB (SQLite/WatermelonDB) ↕ Offline messages, message drafts

Key Design Decisions

  • WebSocket for real-time: Persistent connection for message delivery. Fall back to polling if WS fails.
  • Optimistic sending: Show message instantly with "pending" status, update to "sent" on server ack.
  • Message deduplication: Client-generated UUID per message prevents duplicates on retry.
  • Inverted FlatList: Use inverted={true} on FlatList β€” new messages at bottom, list grows up naturally.
  • Read receipts: Emit read event when message is visible (use IntersectionObserver equivalent / FlatList viewableItems).
  • Push notifications: When WS connection is closed (app backgrounded), use FCM/APNs push.
// Message with optimistic update pattern
function useSendMessage(chatId) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (content) => api.sendMessage(chatId, content),

    // Optimistic update β€” show message immediately
    onMutate: async (content) => {
      const tempMessage = {
        id: uuid(),          // client-generated ID
        content, chatId,
        status: 'pending',
        createdAt: new Date(),
      };
      queryClient.setQueryData(['messages', chatId], (old) => ({
        ...old, messages: [...old.messages, tempMessage]
      }));
      return { tempMessage };
    },

    // Replace temp message with server-confirmed message
    onSuccess: (serverMessage, _, { tempMessage }) => {
      queryClient.setQueryData(['messages', chatId], (old) => ({
        ...old, messages: old.messages.map(m =>
          m.id === tempMessage.id ? serverMessage : m
        )
      }));
    },

    onError: (_, __, { tempMessage }) => {
      // Mark as failed β€” show retry button
      queryClient.setQueryData(['messages', chatId], (old) => ({
        ...old, messages: old.messages.map(m =>
          m.id === tempMessage.id ? { ...m, status: 'failed' } : m
        )
      }));
    }
  });
}
Q17
Design a video feed like TikTok/Instagram Reels in React Native.
Performance React Native UX
β–Ό

Core Technical Challenges

  • Only 1 video plays at a time, 2-3 are preloaded ahead
  • Smooth swipe between full-screen videos (no jank)
  • Memory management β€” can't keep all videos loaded
  • Autoplay on scroll into view

Architecture

// PagerView or FlashList (vertical) β€” one item = full screen
import PagerView from 'react-native-pager-view';

function ReelsFeed() {
  const [activeIndex, setActiveIndex] = useState(0);

  return <PagerView
    style={styles.pager}
    orientation="vertical"
    onPageSelected={(e) => setActiveIndex(e.nativeEvent.position)}
  >
    {reels.map((reel, index) => (
      <ReelItem
        key={reel.id}
        reel={reel}
        // Only active Β±1 items render video player
        isActive={index === activeIndex}
        shouldPreload={Math.abs(index - activeIndex) === 1}
      />
    ))}
  </PagerView>;
}

const ReelItem = React.memo(({ reel, isActive, shouldPreload }) => {
  return <View style={styles.fullScreen}>
    {(isActive || shouldPreload) && (
      <Video
        source={{ uri: reel.videoUrl }}
        paused={!isActive}       // pause when not visible
        repeat={isActive}        // loop only active video
        resizeMode="cover"
        onBuffer={() => showLoadingIndicator()}
      />
    )}
  </View>;
});

Performance Strategies

  • Window: only render Β±1 from active: Unmount videos 2+ away from active to free memory.
  • Preload URLs: Pre-cache video URLs for the next 2 items using Video.prefetch(url).
  • Thumbnail placeholder: Show static thumbnail until video loads to avoid black screen.
  • CDN + adaptive bitrate: Serve HLS streams β€” mobile gets lower quality, WiFi gets HD.
  • Background audio handling: Pause video when app goes to background, handle audio session.
Q18
Design a design system / component library for a large React Native app.
Architecture UX React Native
β–Ό

Design Tokens β†’ Components β†’ Patterns

// 1. Design Tokens β€” single source of truth
const tokens = {
  colors: {
    primary: '#818cf8',
    surface: '#1e1e2e',
    text: { primary: '#e2e8f0', secondary: '#94a3b8' },
    semantic: { error: '#f87171', success: '#34d399' },
  },
  spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 32 },
  typography: {
    sizes: { xs: 11, sm: 13, md: 15, lg: 18, xl: 24 },
    weights: { regular: '400', semibold: '600', bold: '700' },
  },
  radii: { sm: 6, md: 10, lg: 16, full: 9999 },
};

// 2. Themed components using tokens
function Button({ variant = 'primary', size = 'md', label, onPress }) {
  const theme = useTheme();
  return <TouchableOpacity
    onPress={onPress}
    style={[styles.base, styles[variant], styles[`size_${size}`]]}
    accessibilityRole="button"
    accessibilityLabel={label}
  >
    <Text style={styles.label}>{label}</Text>
  </TouchableOpacity>;
}

// 3. Dark mode β€” swap token values, not component styles
const darkTheme = { ...tokens, colors: { ...tokens.colors, surface: '#0f0f1a' } };
const lightTheme = { ...tokens, colors: { ...tokens.colors, surface: '#ffffff' } };

Component Library Structure

  • Atoms: Button, Text, Icon, Avatar, Badge, Input
  • Molecules: Card, ListItem, SearchBar, FormField
  • Organisms: Header, BottomSheet, Modal, PostCard
  • Templates: AuthLayout, TabLayout, ModalLayout
Storybook for RN: Use @storybook/react-native to develop and document components in isolation. It's the gold standard for component libraries and shows great engineering maturity in interviews.
Q19
How do you handle push notifications and background tasks in React Native?
React Native Architecture
β–Ό

Push Notification Architecture

Backend Server β”‚ β–Ό FCM (Android) / APNs (iOS) ← Use a provider like Firebase β”‚ β–Ό Device β”œβ”€β”€ App Foreground β†’ In-app notification handler β”œβ”€β”€ App Background β†’ OS notification center └── App Killed β†’ OS notification center + cold start data
// react-native-firebase/messaging β€” comprehensive solution
import messaging from '@react-native-firebase/messaging';

function setupNotifications() {
  // 1. Foreground notifications
  messaging().onMessage(async (remoteMessage) => {
    showInAppNotification(remoteMessage);
  });

  // 2. App opened from background tap
  messaging().onNotificationOpenedApp((remoteMessage) => {
    navigateToScreen(remoteMessage.data);
  });

  // 3. App opened from killed state
  messaging().getInitialNotification().then((remoteMessage) => {
    if (remoteMessage) navigateToScreen(remoteMessage.data);
  });

  // 4. Background handler (runs even when app is killed)
  messaging().setBackgroundMessageHandler(async (remoteMessage) => {
    updateBadgeCount(remoteMessage);
  });
}
Q20
How do you measure and monitor React Native app performance in production?
Performance Architecture
β–Ό

Observability Stack

What to Measure Tool Key Metrics
Crashes & errors Sentry / Firebase Crashlytics Crash-free rate, error frequency
Performance Sentry Performance / Datadog App start time, slow renders, ANRs
Analytics Firebase Analytics / Mixpanel Screen time, funnel completion
Network Sentry / Charles Proxy (dev) API latency, error rates, payload size
JS bundle Source maps + Sentry Readable stack traces in production

Key Performance Metrics to Track

  • Time to Interactive (TTI): Time from launch to first meaningful user interaction.
  • JS thread FPS: Target 60fps; drops below 55fps are noticeable to users.
  • App start time: Cold start (first install/after killed) vs warm start (from background).
  • Memory usage: Watch for memory leaks, especially with image-heavy screens.
  • Crash-free sessions: Industry standard is 99.5%+ crash-free rate.
// Sentry performance tracing example
import * as Sentry from '@sentry/react-native';

// Measure a critical user flow
const transaction = Sentry.startTransaction({
  name: 'checkout.complete', op: 'user-action'
});

try {
  const span = transaction.startChild({ op: 'api.call', description: 'POST /orders' });
  const result = await createOrder(cart);
  span.finish();
} finally {
  transaction.finish();
}
πŸ’‘ Interview Signal

Mentioning production observability shows senior maturity. Most candidates only talk about dev tools β€” talking about Sentry, crash rates, and real-user monitoring separates you from the pack.

πŸ“± React Native Frontend System Design Guide

20 questions covering Architecture Β· State Β· Performance Β· Navigation Β· Network Β· Offline Β· Security Β· Testing