top of page

React Interview Questions (2026): 30 Expert Answers to Help You Ace Your Next Interview

Updated: Jul 27

React Fundamentals Every Developer Should Know



Desk workspace with laptop showing React code, checklist notebook, mug and sticky note; poster reads Ace Your React Interview 2026.


Ace Your Interview 2026 — Perfect Answers to the Most Common React Interview Questions


There's a particular kind of nervous that only shows up the night before a technical interview (React Interview Questions). Not the loud kind — the quiet kind, where you're lying in bed running through hook rules you've used a hundred times, suddenly unsure if you actually understand them or just know how to make them work.



That gap — between using something and being able to explain it clearly, under pressure, to a stranger evaluating you — is what most interview prep never actually closes. Most guides hand you answers to memorize. What actually gets you through the room is understanding the why well enough that you could explain it a dozen different ways, because the question will rarely arrive exactly as you rehearsed it.


This guide won't pretend that confidence comes from repetition alone. But it will give you something better than a script: answers built the way a senior engineer would actually reason through them out loud, organized the way a real interview loop is usually structured — fundamentals first, because that's the cut line; hooks next, because that's where most conversations either open up or stall; then rendering, state, and the 2026-relevant territory — React 19, the Compiler, Server Components — that increasingly separates candidates who've kept up from candidates who haven't.


Read it once for the answers. Read it again for the reasoning. That second pass is the part that actually shows up in the room.


Part 1: React Fundamentals

1. What is the Virtual DOM, and why does React use it?


The Virtual DOM is a lightweight, in-memory representation of the actual browser DOM. Instead of updating the real DOM directly every time state changes — which is expensive, because DOM operations trigger layout and repaint — React builds a new Virtual DOM tree, compares it to the previous one (a process called reconciliation), and calculates the minimal set of real DOM changes needed. Only that minimal diff gets applied.


The interview trap here is saying "Virtual DOM makes React fast" without qualification. The more accurate answer: it makes updates efficient by batching and minimizing expensive DOM writes — it doesn't make the initial render inherently faster than plain JavaScript, and modern frameworks without a virtual DOM (like Svelte) can be just as fast through different mechanisms. Showing you know the nuance, not just the marketing line, is what separates a strong answer here.


2. Explain the reconciliation process and React's diffing algorithm.


React's diffing runs in O(n) instead of the theoretical O(n³) of a naive tree comparison, because it makes two simplifying assumptions: elements of different types produce entirely different trees (so React unmounts and rebuilds rather than trying to diff a <div> against a <span>), and keys let you hint which children have persisted across renders, which stayed, and which are new. That second assumption is why key management in lists isn't cosmetic — it directly determines whether React reuses a component instance (preserving its state) or destroys and recreates it.


3. Why should you never use array index as a key?


Because the key's job is to give React a stable identity for an item across renders — and an index isn't stable if the array can be reordered, filtered, or have items inserted/removed from the middle. If you delete item 2 out of 5, every item after it shifts index, and React will match old component instances to the wrong data, silently carrying over stale internal state (a checked checkbox that jumps to the wrong row is the classic symptom). Use a unique, stable identifier from your data — a database ID, not the array position.


4. What's the difference between controlled and uncontrolled components?


A controlled component has its value driven entirely by React state — the input's value prop and an onChange handler that updates state on every keystroke, making React the single source of truth. An uncontrolled component lets the DOM manage its own internal state, and you read the current value only when needed, typically via a ref. Controlled components give you validation, conditional disabling, and formatting on every change, at the cost of a re-render per keystroke. Uncontrolled components are simpler and more performant for large, simple forms where you don't need to react to every change — which is part of why React 19's Actions and useActionState lean back toward a more uncontrolled-friendly model for form submission.


5. What are React Fragments, and why use them over a wrapping <div>?


A Fragment (<>...</> or React.Fragment) lets you group multiple children without adding an extra node to the actual DOM. This matters more than it sounds — unnecessary wrapper divs break CSS Grid and Flexbox layouts that depend on direct parent-child relationships, and they bloat the DOM tree in deeply nested component trees. The keyed version, <React.Fragment key={id}>, matters specifically when you're returning fragments from a .map() call inside a list.


6. What is prop drilling, and how do you avoid it?


Prop drilling is passing a prop through several layers of components that don't themselves use it, purely so a deeply nested child can access it. It's not inherently wrong for two or three levels — that's often more readable and traceable than the alternative. It becomes a problem past that, and the fix depends on the shape of the data: Context API for state that's genuinely global to a subtree (theme, auth, locale), component composition (passing children as props to avoid drilling through intermediate layers), or a dedicated state manager like Zustand or Redux for state with complex update logic shared widely across the app.


Part 2: Hooks

7. What problem were hooks actually introduced to solve?


Before hooks, stateful logic could only live in class components, and sharing that logic between components meant Higher-Order Components or render props — both of which produced "wrapper hell," where debugging meant tracing through five layers of nested wrapping components that existed purely for logic reuse, not UI. Hooks let you extract stateful logic into a plain function (a custom hook) that any function component can call directly, with no wrapping and no this binding confusion. The real answer isn't "hooks let you use state in functions" — it's that they made logic reuse compositional instead of structural.


8. What are the Rules of Hooks, and why do they exist?


Only call hooks at the top level — never inside loops, conditions, or nested functions — and only call them from React function components or other custom hooks. This isn't stylistic. React tracks hooks by the order they're called in on each render, not by name, using an internal linked list tied to the component's fiber. If a hook call is conditionally skipped on one render but not another, the order shifts, and React attaches the wrong state or effect to the wrong hook call entirely. The eslint-plugin-react-hooks rule exists because this bug is silent until it isn't — it won't throw an error, it'll just corrupt state in a way that's brutal to trace back to its cause.


9. Explain useEffect and its dependency array in depth.


useEffect runs a function after React commits changes to the DOM, making it the place for anything that reaches outside React's own rendering — subscriptions, data fetching, manually interacting with the DOM, timers. The dependency array controls when it re-runs: omit it, and the effect runs after every render; pass an empty array, and it runs once after the initial mount; pass values, and it re-runs whenever any of those values change between renders (compared by reference for objects and arrays, which is the source of most "why does my effect run every render" bugs — a new object literal is a new reference every time, even with identical contents).


The return function inside useEffect is the cleanup function, called before the effect re-runs and when the component unmounts — critical for clearing subscriptions, timers, or event listeners to avoid memory leaks and stale closures acting on an unmounted component.


10. What's a stale closure, and how does it happen with hooks?


A stale closure happens when a function — usually inside useEffect or an event handler — captures a variable's value at the time the closure was created, and that value never updates even though the actual state has changed since. Classic example: a setInterval inside useEffect with an empty dependency array, referencing a state variable — the interval callback keeps referencing the state value from the render it was created in, forever, because the effect never re-ran to create a new closure with the updated value. Fixes include adding the variable to the dependency array (so a fresh closure is created when it changes), using the functional form of a state setter (setCount(c => c + 1), which doesn't need to read the current value from the closure at all), or storing the latest value in a ref that the closure reads from instead.


11. useMemo vs useCallback — what's actually different?


Both exist to preserve referential equality across renders so dependent computations or child components don't re-run or re-render unnecessarily. useMemo memoizes the return value of a function — use it when a calculation is genuinely expensive and its inputs haven't changed. useCallback memoizes the function reference itself — use it when passing a callback to a child wrapped in React.memo, or as a dependency of another hook, where a new function reference on every render would otherwise defeat the memoization or trigger an effect unnecessarily. The honest caveat worth stating in an interview: overusing either for cheap computations adds memory and comparison overhead for no real gain — and as of React 19, the React Compiler auto-memoizes most of this at build time, which changes when you should reach for these manually at all (more on that below).


12. When would you write a custom hook, and what's the naming convention for?


Write a custom hook the moment you find yourself copying the same combination of useState + useEffect (or any hook composition) across two or more components — a custom hook extracts that logic into a reusable function that returns whatever state and handlers the consuming component needs, without dictating any UI. The use prefix isn't just convention — the linter and React itself rely on it to know a function follows the Rules of Hooks and can safely call other hooks internally.


13. What is useRef, and how is it different from state?


useRef returns a mutable object ({ current: ... }) that persists across the component's entire lifetime without causing a re-render when its value changes — unlike useState, where every update triggers a re-render. Use it for values that need to persist between renders but shouldn't drive UI directly: DOM node references, previous-value tracking, timer IDs, or any mutable value where triggering a re-render on change would be wasteful or wrong. As of React 19, ref can be passed as a regular prop directly to function components, which removed most of the need for forwardRef in everyday code.


14. Explain useContext and when it becomes a performance problem.


useContext subscribes a component to a Context value, re-rendering it whenever that value changes — but critically, every consumer re-renders on any change to the context value, even if the specific consumer only cares about one field of a larger object. This is the classic Context performance trap: a single context holding { user, theme, notifications } means updating notifications re-renders every component reading theme too. The fix is splitting contexts by concern (separate ThemeContext, UserContext) so components only subscribe to what actually affects them, or memoizing the consuming component so it can bail out when the specific slice it reads hasn't changed.





Part 3: Rendering, Performance, and Architecture

15. How does React.memo work, and when does it not help?


React.memo wraps a component and skips re-rendering it if its props are shallowly equal to the previous render's props. It doesn't help — and can actively hurt — when the component receives new object, array, or function props on every parent render (a new reference fails the shallow equality check even if the contents are identical), or when the component is cheap to render anyway, in which case the comparison overhead costs more than the render it's preventing. React.memo is a targeted tool for expensive components receiving genuinely stable props, not a default wrapper for every component in your tree.


16. What causes unnecessary re-renders, and how do you actually diagnose them?


The most common causes: a parent re-rendering and passing new object/array/function references as props each time; Context value changes triggering every consumer regardless of relevance; and state lifted higher in the tree than it needs to be, causing broad re-render cascades for narrow updates. Diagnosing this properly means using the React DevTools Profiler to record an interaction and inspect the flame graph for components that re-rendered without their rendered output actually changing — guessing from reading code alone is unreliable once an app has any real depth.


17. Explain code-splitting and React.lazy.


Code-splitting breaks your JavaScript bundle into smaller chunks loaded on demand instead of all upfront, reducing initial load time. React.lazy(() => import('./Component')) combined with Suspense lets you defer loading a component's code until it's actually needed — typically route-based (each page is its own chunk) or behind an interaction (a modal's code doesn't load until a user opens it). The tradeoff is a brief loading state on first access to that chunk, which Suspense's fallback UI is designed to handle gracefully.


18. What is Concurrent Rendering, and what problem does it solve?


Concurrent rendering lets React interrupt, pause, resume, or abandon a render in progress in response to something more urgent — like user input — instead of rendering being an all-or-nothing synchronous block that freezes the UI until it finishes. This is what powers features like useTransition, which lets you mark a state update as non-urgent so React can keep the interface responsive to typing or clicking while a heavier update (like filtering a large list) renders in the background without blocking the main thread.


19. Explain useTransition and useDeferredValue — and how they differ.


Both exist to keep an interface responsive during expensive updates, but they attack the problem from different ends. useTransition wraps the state update itself, marking it non-urgent so React deprioritizes that specific re-render behind more urgent ones like a keystroke. useDeferredValue instead wraps a value you're already receiving, giving you a version of it that lags behind intentionally during heavy renders, letting the rest of the UI stay responsive while the deferred value catches up shortly after. Use useTransition when you control the state update causing the lag; use useDeferredValue when you're receiving a prop or value you don't control the update timing of.


20. How would you approach optimizing a React app that's rendering slowly?


Start by profiling, not guessing — the Profiler tells you which components are re-rendering and how expensive each render actually is, which is frequently not where intuition points. From there: memoize expensive computations and stable callbacks where profiling shows real benefit, push state down to the lowest component that actually needs it instead of hoisting everything to a shared ancestor, virtualize long lists so you're only rendering visible rows, split Context by concern, and code-split routes and heavy, rarely-used components. The sequence matters — optimizing before profiling is how teams end up with a codebase full of useMemo calls that make things harder to read without making anything faster.


Part 4: State Management

21. When do you reach for Context vs. a dedicated state library?


Context is well-suited to state that's genuinely global but changes infrequently — theme, authenticated user, locale — where the re-render cost of a change is low because the change itself is rare. It becomes the wrong tool for frequently-updating, widely-shared state (like a shopping cart updated on every click, read by a dozen components), because every update re-renders every consumer. In 2026, the practical default for most teams has shifted toward lighter libraries like Zustand for client state and TanStack Query for server state — treating "data from an API" and "local UI state" as two genuinely different problems that deserve different tools, rather than forcing one state solution to handle both.


22. What's the actual difference between client state and server state, and why does the distinction matter?


Client state is state your app owns and controls entirely — form input, a modal's open/closed status, a toggle. Server state is a local cache of data that actually lives somewhere else — and it comes with problems client state doesn't: it can go stale, it needs refetching, it can be requested by multiple components simultaneously, and it benefits from caching, deduplication, and background refresh. Treating server state like client state (dumping fetch results into useState and managing loading/error/caching by hand) is why most homegrown data-fetching logic ends up reinventing a worse version of what libraries like TanStack Query already solve.


Part 5: React 19 and Modern React

23. What does the React Compiler actually do, and does it mean you should stop using useMemo and useCallback?


The React Compiler is a build-time tool, stable as of React 19, that analyzes your component code and automatically inserts memoization where it determines the output won't change — effectively giving standard, unoptimized-looking code the performance profile that used to require manual useMemo/useCallback/React.memo scattered everywhere. The honest answer to whether you can stop using them entirely: mostly, in compiler-enabled projects, for newly written code — but the compiler relies on your components following the Rules of Hooks and being pure (no mutating props or state directly); violate that, and the compiler silently skips optimizing that component rather than throwing an error, which is exactly the kind of nuance that separates a candidate who's actually used it from one repeating a headline.


24. Explain Actions, useActionState, and useFormStatus.


React 19's Actions give async functions passed to a <form>'s action prop first-class handling of pending states, errors, and optimistic updates without the tangle of manual useState calls tracking isSubmitting, error, and data separately. useActionState wraps an action and returns its current state alongside a pending flag, updated automatically as the action runs. useFormStatus lets a child component (like a submit button) read the pending state of its parent form without that state being explicitly passed down as a prop — useful for a reusable submit button that needs to disable itself during submission regardless of which form it's inside.


25. What is the use hook, and how is it different from useContext or a normal await?


use lets you read the value of a resource — a Promise or a Context — directly during render, and unlike other hooks, it can be called conditionally and inside loops, because it doesn't rely on call-order tracking the way stateful hooks do. Called with a Promise, it integrates with Suspense: the component suspends until the Promise resolves, rather than you manually managing a loading state. Called with a Context, it behaves like useContext but without the top-level-only restriction. The distinction worth naming clearly: use is not a replacement for useEffect-based fetching in every case — it's specifically for consuming a promise that's already in flight (often initiated by a Server Component), not for triggering a fetch on its own.


26. What are React Server Components, and how are they different from Server-Side Rendering?


Server-Side Rendering (SSR) generates the initial HTML on the server, but the full component code still ships to the client and re-runs there during hydration. React Server Components go further — they run exclusively on the server, their code never ships to the client bundle at all, and they can access backend resources (databases, file systems) directly without an API layer, while streaming their rendered output to Client Components that handle interactivity. The practical distinction to draw in an interview: SSR solves first-paint speed; Server Components solve bundle size and direct backend access, and the two are complementary, not competing — most modern frameworks (Next.js's App Router being the primary example) use both together.


27. What is hydration, and what commonly goes wrong with it?


Hydration is the process where React takes server-rendered static HTML and attaches event listeners and internal state to it on the client, making already-visible markup interactive without re-rendering it from scratch. The most common failure is a hydration mismatch — where the server-rendered HTML and the client's first render produce different output (often from Date.now(), Math.random(), or browser-only APIs like window being referenced during a render that also runs on the server), which forces React to discard the mismatched section and re-render it client-side, hurting both performance and, in visible cases, causing a jarring flash of different content.


Part 6: Testing and Practical Judgment

28. How do you approach testing a React component, and what should you avoid testing?


Test behavior, not implementation. Tools like React Testing Library are built around this philosophy deliberately — they encourage querying the rendered output the way a user would encounter it (by role, label, or visible text) rather than reaching into component internals or state directly. Avoid testing implementation details like internal state variable names or which specific hook was called — those are refactor-fragile: change how a component achieves a result without changing what a user experiences, and implementation-coupled tests break for no real reason, training a team to distrust their own test suite.


29. A component re-renders when it shouldn't. Walk through how you'd debug it, live.


Open React DevTools Profiler, record the interaction, and look at which components lit up in the flame graph — that immediately narrows the search from "somewhere in this app" to specific components. For each one, check what changed between renders: new object/array/function props from a parent, a Context value shift, or state that didn't actually need to live that high in the tree. If it's a prop reference issue, the fix is usually memoizing the value at its source rather than the consuming component. If it's Context, the fix is usually splitting the context or memoizing the consumer. Talking through this sequence out loud — profile first, diagnose the actual cause, then apply the narrowest fix — demonstrates real debugging instinct far more convincingly than naming a memorized list of optimization techniques.


30. What would you actually look for when reviewing a junior developer's React pull request in 2026?


Whether hooks are used correctly and at the top level; whether keys in lists are stable, meaningful identifiers rather than array indices; whether state lives at the right level in the tree rather than being over-lifted or over-localized; whether Server Components and Client Components are split sensibly (interactivity and browser APIs pushed to the smallest possible Client Component boundary); and increasingly, whether AI-assisted code has actually been understood by the person submitting it — a plausible-looking hook dependency array or a Context usage that "works" in the happy path but ignores the re-render cost is one of the more common failure patterns showing up in reviews now that so much boilerplate gets generated rather than typed by hand.


The Part Prep Guides Don't Usually Say


None of this — not one answer above — will matter as much in the room as your ability to say "I'm not certain, but here's how I'd reason through it." Interviewers who've been in this seat for years aren't actually testing whether you've memorized the Compiler's exact behavior. They're testing whether you can think clearly under mild pressure about something you partially understand, because that's what the job actually is, every single week, forever. Nobody ships code where they already knew every answer in advance.


So walk in knowing these answers cold enough that you don't have to perform recall — you can just talk, the way you'd explain it to a teammate over coffee. That shift, from reciting to reasoning, is the entire difference between a candidate who sounds prepared and one who sounds like they already do this for a living.


You already do. This is just the conversation where you get to say so out loud.

Comments


FLAIR

© 2026 A bevociferous Production. All rights reserved.

  • Facebook
  • Instagram
  • X
  • Linkedin
  • Youtube

© BeVociferous — Speak Your Vociferous Mind

THE

bottom of page