React interview questions
Reviewed by Mark Dickie · Last updated
React is a JavaScript library for building user interfaces from reusable components, using a virtual DOM to reconcile changes efficiently. For interviews, you need to be solid on hooks (useState, useEffect, useMemo, useCallback, useRef), component lifecycle and rendering behavior, state management approaches, performance optimization, and common patterns like composition and custom hooks. Interviewers also test whether you understand why React re-renders, how the dependency array works, and when lifting state or using context is the right call.
What does a React interview typically test?
Most React interviews focus on a predictable set of areas. The table below maps the common topics to what interviewers are really probing:
| Topic | What gets tested | Common pitfalls |
|---|---|---|
| Hooks | Correct use of useState, useEffect, useMemo, useCallback, useRef | Missing or wrong dependency arrays, stale closures |
| Rendering | Reconciliation, key props, memoization | Unnecessary re-renders, index keys in lists |
| State management | Local vs lifted vs context vs external store | Overusing context, prop drilling past 2–3 levels |
| Effects | Side effects, cleanup, fetch patterns | Effect loops, cleanup not returned, fetch race conditions |
| Patterns | Custom hooks, composition, render props, HOCs | Reinventing built-in patterns, over-abstraction |
How should you prepare for a React interview?
- Write small components from memory — a controlled form, a data-fetching hook, a list with keys — without referencing docs.
- Study the React rendering pipeline: understand what triggers a re-render, how React batches state updates, and when children re-render.
- Practice explaining the rules of hooks and why they exist; interviewers love asking you to spot a broken hooks call.
- Build at least one custom hook (e.g. a debounce or intersection-observer hook) and be ready to walk through the code.
- Review performance trade-offs: know when useMemo and useCallback actually help and when they add overhead for nothing.
What are the most common mistakes candidates make?
Stale closures inside useEffect trip up a lot of candidates — the effect captures variables from the render it was created in, so a missing dependency means you are reading an old value. Another frequent issue is reaching for useMemo or useCallback on every function, which adds comparison overhead without preventing re-renders unless the child component is actually memoized with React.memo. Index-as-key in lists still comes up, and it causes subtle bugs when items are reordered or removed.
Key facts
- Tarmac has 141 React interview questions on this topic, 10 of them on this page, at difficulty 1–4 of 5.
- Tarmac last reviewed these React interview questions on 18 August 2026.
At a glance
| Questions | 10 shown · 141 in the bank |
|---|---|
| Difficulty | 1–4 of 5 |
| Formats | Fill in the blank, Code output, Flashcard, Short answer, Multiple choice, Find the bug, Multiple answer, Ordering, True / false |
What you'll review
- external stores
- reconciliation
- lifting state
- keys
- usecontext
- useeffect deps
Practice questions
React/state/external-stores
Complete the useSyncExternalStore call below so the component correctly subscribes to myStore.#
Show answer
Complete the useSyncExternalStore call below so the component correctly subscribes to myStore.
import { **useSyncExternalStore** } from 'react';
function Counter() {
const count = **useSyncExternalStore**(
myStore.**subscribe**,
myStore.getSnapshot
);
return <div>{count}</div>;
}
The hook is useSyncExternalStore and its first argument is a subscribe function that React calls to register a callback notified on store changes. The second argument is getSnapshot, which returns the current store value. Filling in useSyncExternalStore for {{0}} and subscribe for {{1}} produces a correctly wired external-store subscription.
React/rendering/reconciliation
What is rendered to the screen (as visible UI text) the first time this component mounts in a standard React 18 production environment (no Strict Mode)?#
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
console.log('render');
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count)}>Click me</button>
</div>
);
}
export default Counter;Show answer
0
Click me
On the initial mount the component renders once. useState(0) initialises count to 0, so <p>{count}</p> displays 0 and the button displays its label Click me. The question asks only about the visible DOM output on first mount, which is unambiguous regardless of Strict Mode or bailout behaviour: the user sees 0 and the button text Click me.
React/state/lifting-state
What is the difference between state and props?#
Show answer
Props are inputs passed in by a parent and are read-only to the receiving component. State is data the component owns and can change over time (via its setter), triggering a re-render.
Data flows down as props; a component manages its own state. Lifting state up means moving shared state to a common ancestor and passing it back down as props.
React/rendering/keys
Why does React need a key on list items, and what makes a good key?#
Show answer
Keys give each list item a stable identity so React can match elements across renders during reconciliation, preserving their state and minimizing DOM work. A good key is stable and unique among siblings — typically a data id — not the array index when the list can reorder.
Without stable keys React falls back to index matching, which moves state to the wrong items when the list changes. Keys are about identity, not ordering.
React/hooks/usecontext
Consider the following component tree:#
Options
Show answer
ThemedButton logs once per click — React.memo does not prevent the re-render. The Provider's value={{ theme, setTheme }} creates a fresh object literal on every App render, so useContext sees a changed reference and triggers a re-render of every consumer regardless of whether theme actually changed. React.memo only guards against prop-based bails, but the re-render here is driven by the context subscription, not by props.
When a context value changes, React re-renders every component that calls useContext with that context — regardless of whether the specific slice of data the component uses has changed. This is because useContext performs a reference equality check on the entire context value object. If the Provider's value is a new object literal on every parent render (e.g., value={{ user, setUser }}), all consumers re-render even if user hasn't changed. The only built-in escape hatch is to memoize the value with useMemo (or split contexts). React.memo on the consumer does NOT help because useContext bypasses the props-based bailout — the re-render is triggered by the context subscription, not by prop changes.
React/hooks/usecontext
The AuthProvider below causes all context consumers to re-render unnecessarily on every parent render, even when user and dispatch haven't changed. Identify the buggy line.#
function AuthProvider({ children }) {
const [user, dispatch] = React.useReducer(authReducer, null);
const value = { user, dispatch };
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}Show answer
The bug is on line 4.
The bug is on line 4: const value = { user, dispatch }; creates a new object reference on every render of AuthProvider. Because useContext compares context values by reference (Object.is), every consumer re-renders whenever AuthProvider re-renders for any reason — even if user and dispatch haven't changed. The fix is to wrap the value in useMemo: const value = React.useMemo(() => ({ user, dispatch }), [user, dispatch]);. The dispatch function from useReducer is stable across renders, and user changing is a legitimate reason to re-render consumers, so the memoized value only changes when it should.
React/hooks/usecontext
You have a large React application using a single AppContext that holds both state (frequently updated) and dispatch (stable). Many components deep in the tree only need to call dispatch but are re-rendering on every state change because they consume AppContext via useContext.#
Options
Pick every one that applies.
Show answer
- Split
AppContextinto two separate contexts —AppStateContextandAppDispatchContext— so dispatch-only consumers subscribe only to the stable dispatch context. - Pass
dispatchdown exclusively via a separateDispatchContext.Providerwhose value is memoized withuseMemo(() => dispatch, [dispatch])(or simply set once), so its reference never changes.
The correct pattern for avoiding unnecessary re-renders with complex context state is to split the context into a state context and a dispatch/setter context. Dispatch from useReducer is guaranteed stable (same reference every render), so components that only need to dispatch can consume the dispatch context and will never re-render due to state changes. Components that need state consume the state context and only re-render when state changes. Using a single combined context, even with useMemo, still re-renders all consumers on any state change. Using Redux or external state is not a React context technique. Passing values via props defeats the purpose of context. The multi-select correct answers are: splitting into separate state and dispatch contexts, and memoizing expensive child subtrees with React.memo/useMemo where appropriate.
React/hooks/useeffect-deps
Consider the following component:#
Options
Show answer
The effect fires on every render and triggers an infinite re-render loop. The dependency array contains the object literal { id: userId }, which is a new reference each time the component renders. React compares dependencies with Object.is (shallow referential equality), so even though userId hasn't changed, the fresh object reference makes React think a dependency changed, re-running the effect — and the setState inside sends it into a loop. Passing userId directly as the dependency, or memoizing the object with useMemo, fixes it.
React's useEffect dependency array uses Object.is (shallow referential equality) to compare each dependency between renders. An object literal { id: userId } is created fresh on every render, so its reference changes every time — even if userId hasn't changed. This causes the effect to re-run on every render, producing an infinite loop when combined with a setState call inside. The fix is to pass userId directly as the dependency (a primitive), or memoize the object with useMemo. Options about deep equality or batching are common misconceptions; React does not perform deep equality checks on deps.
React/hooks/useeffect-deps
A component has the following effect:#
Put these in order
Show answer
effect 0cleanup 0effect 1cleanup 1
The useEffect cleanup function returned from a prior effect run is called before the next effect fires (when deps change) and also on unmount. So the sequence is: mount → effect-1 runs; dep changes → cleanup-1 runs, then effect-2 runs; unmount → cleanup-2 runs. This ordering is critical for avoiding race conditions, for example cancelling a previous fetch before starting a new one. The AbortController pattern exploits this: the cleanup aborts the in-flight request from the previous render cycle.
React/rendering/reconciliation
Consider the following React code:#
Options
Show answer
Child will re-render every time the button is clicked, so the statement is False. Although items is stabilized by useMemo and passes React.memo's shallow comparison, the style prop is an inline object literal ({ color: 'red' }) that creates a new reference on every parent render. React.memo performs referential equality checks (===) on each prop, so the changed style reference causes Child to re-render despite the memo wrapper.
This question tests understanding of React's fiber bailout mechanism and referential equality. React.memo wraps Child and performs a shallow prop comparison. On the second render triggered by setCount, style is an object literal { color: 'red' } created inline. Even though its shape and values are identical to the previous render's object, it is a new reference each time — so the shallow comparison (===) fails, and Child re-renders. items is defined with useMemo and has no changing dependencies, so it retains the same reference and passes the memo check alone. But because style fails, Child re-renders regardless. The fix would be to also memoize style with useMemo or useCallback. The correct answer is that Child does re-render on every parent render, despite React.memo.
Sources
The official documentation these questions are checked against:
Related interview questions
The other 131 questions
This page shows 10. A free account opens the rest and marks your answers, which a page of answers can't do. Miss one and it returns a few days later, then a week after that, until you stop missing it.
Free · the whole bank · 100 graded answers per 30 days · written feedback and job-ad quizzes on the paid plan