Senior React interviews go beyond the basics. Interviewers look for deep understanding of architectural patterns, performance optimization, and the inner workings of React.
RSCs are a new paradigm where components render exclusively on the server and send zero JavaScript to the client. This dramatically reduces bundle size. Client Components, marked with "use client", are the traditional React components that support interactivity, hooks, and browser APIs. The key insight is that RSCs can fetch data directly (even from databases) without exposing credentials to the browser.
React uses a "virtual DOM" to optimize UI updates. When state changes, React creates a new virtual tree and compares it (diffs it) with the previous one. This process is called reconciliation. The modern "Fiber" architecture makes these updates interruptible and prioritized, allowing React to pause work on less important updates to handle urgent ones first.
useMemo memoizes the result of a calculation to avoid expensive re-computations on every render. useCallback memoizes the function instance itself to prevent unnecessary re-renders of child components that rely on reference equality. Both are performance optimizations—use them when you've identified actual performance issues, not preemptively.
Portals render children into a DOM node that exists outside the parent's hierarchy. Common use cases include modals, tooltips, and dropdown menus that need to break out of overflow: hidden containers or z-index stacking contexts. Events still bubble through the React tree, not the DOM tree.
Error boundaries are class components that catch JavaScript errors anywhere in their child component tree. They use getDerivedStateFromError() to render a fallback UI and componentDidCatch() for logging. Important: they don't catch errors in event handlers, async code, or server-side rendering—only during rendering.
React.memo is a higher-order component that prevents re-rendering if props haven't changed (shallow comparison). It's a performance optimization for functional components. Don't wrap everything in memo—the comparison itself has a cost. Profile first, optimize second.
The Virtual DOM is a lightweight JavaScript representation of the real DOM. Direct DOM manipulation is slow because it triggers browser reflows and repaints. React batches changes to the Virtual DOM, diffs them efficiently, and updates the real DOM minimally. This abstraction is why React feels fast.
Context API is built-in and ideal for low-frequency global updates: theme, user auth, locale. Redux (or alternatives like Zustand) is better for complex state logic, frequent updates, middleware (logging, async), and time-travel debugging. Context re-renders all consumers on any change; Redux allows selective subscriptions.
Prop drilling is passing data through many component layers that don't use it. It makes code hard to maintain. Solutions: Context API for global state, Component Composition (passing components as children/props), or state management libraries. Composition is often overlooked but powerful.
Keys help React identify which list items changed, were added, or removed. Stable, unique keys let React reuse DOM elements efficiently. Using array indexes as keys is problematic when the list order changes—it causes bugs and performance issues because React can't tell items moved.
Controlled: React state is the single source of truth for form inputs. You handle changes via onChange. Uncontrolled: The DOM manages form state; you access values via refs. Controlled components give you more control (validation, formatting) but require more code.
useEffect replaces class lifecycle methods: useEffect(..., []) = componentDidMount. useEffect(..., [deps]) = componentDidUpdate. The cleanup function = componentWillUnmount. The mental model shift: think in terms of synchronizing effects with state, not lifecycle events.
React.lazy enables dynamic imports for code-splitting—components are loaded only when needed. Suspense shows a fallback UI while loading. This reduces initial bundle size significantly. In React 18+, Suspense also works for data fetching with compatible libraries.
Higher-Order Components wrap components to share logic but create "wrapper hell" and make debugging difficult. Hooks (like useAuth(), useTheme()) let you reuse stateful logic without changing component hierarchy. Hooks also have better TypeScript support and are the modern pattern.
Omitting variables from the dependency array causes stale closures—your effect sees old values. Always follow the linter. If it's hard to get dependencies right, it often means the logic needs restructuring: extract the function inside the effect, use useCallback, or consider useReducer for complex state.