React Interview Questions & Answers
React interview questions covering components, hooks, state management, and performance -- everything a frontend interview panel expects a React developer to know.
18 Questions
~27 min read
Beginner: 3
Intermediate: 10
Advanced: 5
Coding 5
Props are read-only data passed from a parent component; state is data owned and managed inside a component that can change over time and triggers a re-render when updated.
Detailed Answer
Props flow one-way, from parent to child, and a component should never modify its own props -- if it needs different data, the parent passes new props. State is local to a component (or lifted up to a shared ancestor) and is updated via setState/useState, which schedules a re-render with the new value. A component can be purely presentational, driven entirely by props, or stateful, managing its own internal data.
useState holds a piece of local component state across re-renders; useEffect runs side effects (data fetching, subscriptions, DOM manipulation) after render, optionally re-running when its dependencies change.
Detailed Answer
`const [count, setCount] = useState(0)` gives you a value and a setter that triggers a re-render when called. `useEffect(() => { fetchData(); }, [id])` runs the fetch after the component mounts and again whenever `id` changes, because it's listed in the dependency array. An empty dependency array (`[]`) means the effect runs only once, on mount; omitting the array entirely means it runs after every render.
Best Practices
Always include every value the effect reads from component scope in the dependency array (or use a linter rule like exhaustive-deps) to avoid stale closures.
Common Mistakes
Omitting a value from useEffect's dependency array to 'stop it from re-running,' which causes the effect to silently use a stale, captured version of that value instead.
Use JavaScript expressions directly in JSX -- ternaries, && short-circuiting, or early returns -- rather than a template-specific directive.
Detailed Answer
Because JSX is just JavaScript, you can write `{isLoggedIn ? : }` for either/or rendering, `{items.length > 0 && ;`) before the main render output.
Best Practices
Be careful with && for numeric conditions -- `{count && }` renders a literal 0 on the page when count is 0, since 0 is falsy but still gets rendered as text; use `{count > 0 && ...}` instead.
Common Mistakes
Writing `{count && }` and seeing a stray '0' rendered on the page when count is zero.
Trigger the fetch inside useEffect on mount (or when a dependency changes), track loading/data/error in state, and render conditionally based on which state is active.
Detailed Answer
A typical pattern: `const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);` then inside `useEffect`, call the API, and set data/loading/error accordingly, including in a try/catch so failures update the error state instead of throwing unhandled. Cleanup (e.g. an AbortController) matters if the component can unmount or the dependency can change before the request finishes.
Best Practices
Guard against setting state after the component has unmounted (a stale request completing late) by using a cleanup function or an AbortController.
Common Mistakes
Fetching data without a cleanup/cancellation mechanism, causing a 'setState on an unmounted component' warning or, worse, a stale response overwriting newer data.
JSX is a syntax extension that looks like HTML inside JavaScript; a compiler (Babel) transforms it into plain React.createElement() calls (or the newer automatic JSX runtime) before it reaches the browser.
Detailed Answer
Browsers don't understand JSX natively -- `
{title}
` is compiled into something like `React.createElement('div', {className: 'card'}, title)`, which returns a plain JavaScript object describing that element. This is why JSX requires a build step (Babel, or a bundler with a JSX transform) and why you can embed any JavaScript expression inside curly braces -- it's ultimately just function calls, not a templating language with its own separate rules.Conceptual 8
The Virtual DOM is an in-memory representation of the real DOM; React diffs it against the previous version and applies only the minimal set of real DOM updates needed.
Detailed Answer
Directly manipulating the browser DOM is comparatively slow, especially with frequent updates. React keeps a lightweight virtual representation of the UI tree, and whenever state changes, it builds a new virtual tree, diffs it against the previous one (reconciliation), and applies only the actual changes to the real DOM in a batched update -- avoiding unnecessary, expensive re-renders of unaffected elements.
Hooks let function components use state and other React features (previously only available in class components) without writing a class.
Detailed Answer
Before hooks, stateful logic required class components with lifecycle methods (componentDidMount, componentDidUpdate, etc.), and reusing stateful logic across components meant patterns like higher-order components or render props, which added wrapper layers and made component trees harder to follow. Hooks like useState, useEffect, and custom hooks let you extract and reuse stateful logic directly as functions, keeping components flatter and logic more composable.
Common Mistakes
Calling hooks conditionally or inside loops, which breaks React's assumption that hooks are called in the same order on every render.
A controlled component's value is driven entirely by React state (via value + onChange); an uncontrolled component keeps its own internal DOM state, read via a ref when needed.
Detailed Answer
With a controlled input, every keystroke updates state via onChange, and the input's displayed value always comes from that state -- giving React full visibility and control over the form data at all times. An uncontrolled input just lets the browser manage its own value internally, and you read it on demand with a ref (e.g. on form submit), which is simpler for basic cases but harder to validate or react to in real time.
Best Practices
Use controlled components when you need real-time validation, conditional rendering based on input, or to synchronize the value elsewhere; uncontrolled components are fine for simple, one-shot forms.
Context lets you share a value across a component tree without manually passing props through every intermediate level; use it for broadly-needed data like theme, auth, or locale.
Detailed Answer
Prop drilling means passing a prop down through several layers of components that don't themselves use it, just to get it to a deeply nested child. Context (createContext + a Provider) lets any descendant read the value directly via useContext, skipping the intermediate layers. It's well suited to genuinely global, rarely-changing data; for frequently-changing state shared across many components, a dedicated state management library often scales better, since every context consumer re-renders on any context value change.
Best Practices
Split contexts by concern (e.g. separate ThemeContext and AuthContext) rather than one giant context object, so unrelated consumers don't re-render on unrelated changes.
Common Mistakes
Putting frequently-changing state (like every keystroke of a search box) into a single broad context that many components consume, causing widespread unnecessary re-renders.
Both manage local state; useReducer centralizes update logic in a single reducer function driven by dispatched actions, which scales better for complex or interrelated state transitions.
Detailed Answer
useState is simplest for independent, simple values. useReducer is a better fit when several pieces of state update together in response to the same action, when the next state depends heavily on the previous state in non-trivial ways, or when you want the update logic testable in isolation from the component (a pure reducer function is easy to unit test without rendering anything).
React Router intercepts navigation and swaps rendered components based on the URL without a full page reload, updating the browser's history API to keep the URL and back/forward behavior correct.
Detailed Answer
Instead of the browser requesting a new HTML document from the server on every link click, React Router listens for navigation, prevents the default full-page reload, updates the URL via the History API, and re-renders whichever route component matches the new path -- all within the same loaded page. This gives an app-like feel (instant transitions) but means you need to handle things like scroll restoration, code-splitting per route, and SEO (since content is rendered client-side) deliberately.
Server Components render on the server and send serialized output (not JavaScript) to the client, reducing bundle size and enabling direct backend data access without an API round-trip, while Client Components render (and can be interactive) in the browser as usual.
Detailed Answer
A Server Component can, for example, query a database directly during server rendering and never ships its own logic to the browser, shrinking the client bundle and improving initial load. Client Components are still needed for anything interactive (state, effects, event handlers) and are marked explicitly (e.g. a 'use client' directive in frameworks that support this model). The two compose together: Server Components can render Client Components as children, but not the other way around directly.
Use a testing library like React Testing Library to render the component and assert on what the user would see/do, rather than testing internal implementation details.
Detailed Answer
React Testing Library encourages querying the rendered output the way a user would (by visible text, label, or role) and firing real events (clicks, typing) rather than reaching into component internals or state directly. This makes tests resilient to refactors that don't change user-visible behavior, and catches regressions that actually matter to users.
Best Practices
Test behavior and output, not implementation detail (e.g. don't assert on a component's internal state variable directly; assert on what's rendered).
Common Mistakes
Writing tests that assert on internal state or call internal methods directly, which breaks on harmless refactors and doesn't actually verify user-facing behavior.
Architecture 1
React diffs the new virtual DOM tree against the previous one element-by-element, using each element's type and, within lists, its key, to decide whether to update, replace, or reorder nodes.
Detailed Answer
When element types differ (e.g. a
becomes a ), React tears down the old subtree and builds a new one. When types match, React updates only the changed attributes/children in place. For lists, the `key` prop tells React which array item corresponds to which rendered element across re-renders -- without stable keys (or using array index as key when items can reorder), React can misattribute state to the wrong item or re-render more than necessary.
Best Practices
Use a stable, unique id as the key for list items, not the array index, especially when the list can be reordered, filtered, or have items inserted/removed.
Common Mistakes
Using array index as key for a list that can be reordered or filtered, which causes component state (like an input's value) to attach to the wrong row after the list changes.
Performance 2
React.memo memoizes an entire component (skipping re-render if props are shallow-equal); useMemo memoizes a computed value; useCallback memoizes a function reference.
Detailed Answer
React.memo wraps a component so React skips re-rendering it if its props haven't shallow-changed since the last render. useMemo caches the result of an expensive calculation between renders, recomputing it only when its dependencies change. useCallback caches a function's identity between renders, which matters when that function is passed as a prop to a React.memo-wrapped child -- without it, a new function reference on every render would defeat the memoization.
Best Practices
Only reach for these when profiling shows an actual unnecessary re-render or expensive recomputation -- they add complexity and, used incorrectly, can make performance worse.
Common Mistakes
Wrapping every component in React.memo and every function in useCallback by default, which adds comparison overhead everywhere without measurable benefit, and can even hurt performance.
Common causes are new object/array/function references created on every render being passed as props, context value changes triggering all consumers, and missing memoization on expensive child components.
Detailed Answer
Use the React DevTools Profiler to record a render pass and see which components re-rendered and why. Frequent culprits: passing a new inline object or arrow function as a prop every render (defeating React.memo on the child), a context value that's a new object literal each render (so every consumer re-renders even if the meaningful data didn't change), and state updates placed too high in the tree, causing large subtrees to re-render for a small, localized change.
Best Practices
Keep frequently-changing state as local as possible in the tree, and memoize object/array/function props that are passed to memoized children.
Behavioral 1
A strong answer identifies the specific pain (too much responsibility in one component, tangled state, duplicated logic), extracts smaller pieces (custom hooks, child components) incrementally, and verifies behavior didn't regress along the way.
Detailed Answer
Interviewers want to hear a concrete before/after: what made the component hard to work with (e.g. 15 useState calls, deeply nested conditional JSX, mixed data-fetching and presentation logic), how it was broken apart (extracting a custom hook for the data logic, splitting UI into smaller focused components), and how correctness was verified during the refactor (existing tests, manual QA, or adding tests first).
Scenario-Based 1
Separate the presentational rendering from the data/state logic (often via a custom hook or a headless-table library pattern), so the same table shell can be reused with different data and column configurations.
Detailed Answer
A common design: the table's state (sort column/direction, filter values, current page) lives in a custom hook or the parent, while the table component itself is a mostly-presentational renderer that receives already-sorted/filtered/paginated rows plus column definitions as props, and emits callbacks (onSortChange, onPageChange) rather than owning the logic itself. This separation makes the table reusable across very different datasets without duplicating sorting/filtering code per usage.
Best Practices
Keep the table component headless/presentational and push data-shaping logic (sort, filter, paginate) into a hook or the parent, so the component stays reusable across contexts.
No questions match your filters.
Related Skills
Continue Your Career Journey
Frontend Developer Roadmap
Frontend Developer Salary Guide
React Developer Resume Resume Template
React Developer Mock Interview Mock Interview