Skip to content

Hard React JS Interview Questions

44 hard questions with answers. Design, trade-off and deep-dive questions asked for senior roles and at top product companies.

Topics in the hard set

Performance Optimization: 9 React Hooks: 8 Next.js and Advanced React: 8 State Management: 6 Testing and Debugging: 6 Props and State: 4 Routing and Forms: 2 React Fundamentals: 1
Solved — hardStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

What is React Fiber?

Save to ▾
Show answer
Fiber is the reconciliation engine introduced in React 16. It represents each component as a unit of work (a fiber node) so rendering can be split into chunks, paused, prioritised and resumed. This makes concurrent features possible: urgent updates like typing can interrupt less urgent renders. Rendering happens in an interruptible render phase, then changes are applied in a synchronous commit phase.
02

How does React decide whether to keep or reset a component's state?

Save to ▾
Show answer
State is tied to a component's position in the render tree, not to the variable or JSX line. If the same component type renders at the same position, its state is kept. If the type at that position changes, or its key changes, React destroys the subtree and its state. That is why defining a component inside another component causes state loss on every render.
03

A timer using setInterval inside useEffect always shows 1 and never goes higher. What is the bug?

Save to ▾
Show answer
The effect probably runs once with an empty dependency array and calls setCount(count + 1). The interval callback closes over the count from the first render, which is 0 forever, a stale closure. Fix it with the updater form setCount(c => c + 1), so the callback never needs the current value, and return a cleanup that calls clearInterval to avoid leaks.
04

What is state colocation and why does it matter for performance and maintainability?

Save to ▾
Show answer
State colocation means keeping state as close as possible to the components that use it. Global or high-level state causes large parts of the tree to re-render on every change and makes ownership unclear. If only a dropdown needs isOpen, keep it in the dropdown. Lift state only when siblings must share it. Good colocation often removes the need for memoisation entirely.
05

How would you update deeply nested state cleanly? Show with Immer.

Save to ▾
Show answer
Manual spreading of nested objects is error prone. Immer lets you write mutating code on a draft and produces an immutable copy. Redux Toolkit uses it internally. import { useImmer } from 'use-immer'; const [form, updateForm] = useImmer({ address: { city: '', pin: '' } }); updateForm(draft => { draft.address.city = 'Noida'; }); Also consider flattening state to avoid deep nesting.
06

When would you use useLayoutEffect instead of useEffect?

Save to ▾
Show answer
useEffect runs after the browser paints, which is fine for most side effects. useLayoutEffect runs synchronously after DOM changes but before paint, so it is used when you must measure layout and update immediately, for example positioning a tooltip based on its size, to avoid a visible flicker. It blocks painting, so overusing it hurts performance, and it does not run during server rendering.
07

A search box fetches results on every keystroke and sometimes shows results for an older query. How do you fix this race condition?

Save to ▾
Show answer
Responses can arrive out of order, so a slow old request overwrites newer results. Cancel or ignore stale requests in the effect cleanup. useEffect(() => { const ctrl = new AbortController(); fetch(`/api/search?q=${q}`, { signal: ctrl.signal }) .then(r => r.json()) .then(setResults) .catch(() => {}); return () => ctrl.abort(); }, [q]); Also debounce the input, or use TanStack Query which handles this for you.
08

Explain useTransition and useDeferredValue.

Save to ▾
Show answer
Both are concurrent features that keep the UI responsive. useTransition gives you startTransition to mark a state update as non-urgent, plus an isPending flag, so typing stays smooth while a heavy list re-renders. useDeferredValue takes a value and returns a lagging copy that React updates in the background. Use useTransition when you control the setter, and useDeferredValue when you only receive the value as a prop.
09

What is the use() API introduced in React 19?

Save to ▾
Show answer
use is a React 19 API that reads the value of a promise or a context during render. With a promise, the component suspends until it resolves, showing the nearest Suspense fallback, and errors go to the nearest error boundary. Unlike other hooks, use can be called inside conditions and loops. Promises should come from a framework, cache or server component, not be created fresh on every render.
10

What is useOptimistic and when would you use it?

Save to ▾
Show answer
useOptimistic shows a temporary optimistic state while an async action is running. For example, when a user likes a post, you immediately show the like count increased, call the server, and React automatically reverts to the real state when the action finishes or fails. It makes interfaces feel instant for actions that usually succeed, like sending a chat message or adding to a wishlist.
11

What is useEffectEvent?

Save to ▾
Show answer
useEffectEvent, stable since React 19.2, creates a function that always sees the latest props and state but is not reactive, so it does not need to be in the effect's dependency array. Example: a chat effect reconnects when roomId changes, and an onConnected event shows a notification using the current theme. Wrapping the notification in useEffectEvent stops theme changes from reconnecting the socket.
12

How do you expose a custom method like focus() from a child to a parent in React 19?

Save to ▾
Show answer
In React 19, function components receive ref as a normal prop, so forwardRef is no longer needed. Use useImperativeHandle to expose only selected methods. function SearchInput({ ref }) { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; } })); return <input ref={inputRef} />; } // parent: const r = useRef(); <SearchInput ref={r} />; r.current.focus();
13

What is useSyncExternalStore?

Save to ▾
Show answer
useSyncExternalStore subscribes a component to an external, non-React data source, like a browser API or a custom store, in a way that is safe with concurrent rendering. You give it a subscribe function and a getSnapshot function, and optionally getServerSnapshot. It prevents tearing, where parts of the UI show different values of the same store. Libraries like Redux and Zustand use it internally.
14

What is Redux middleware?

Save to ▾
Show answer
Middleware sits between dispatching an action and the reducer, letting you intercept actions for side effects. Signature: store => next => action => {...}. Common uses are async logic (redux-thunk, included by default in Redux Toolkit), logging, analytics, error reporting and the RTK Query cache middleware. Redux Toolkit also provides listenerMiddleware for reacting to actions, a lighter alternative to redux-saga for most cases.
15

Why normalise state in Redux and what does createEntityAdapter do?

Save to ▾
Show answer
Normalising means storing items in a lookup like { ids: [...], entities: { id: item } } instead of nested arrays. Each item exists once, updates are O(1) and you avoid duplicated data getting out of sync. createEntityAdapter in Redux Toolkit generates this structure plus reducers like addOne, upsertMany and removeOne, and memoised selectors such as selectAll and selectById.
16

What are memoised selectors and why use createSelector?

Save to ▾
Show answer
A selector reads or derives data from the store. If a selector returns a new array each time, such as state.todos.filter(t => t.done), useSelector sees a new reference and re-renders on every store update. createSelector from Reselect, included in Redux Toolkit, caches the result and recomputes only when its input selectors return changed values, avoiding wasted renders and repeated expensive calculations.
17

After adding an AuthContext at the root, profiling shows the whole app re-rendering every few seconds. What would you check?

Save to ▾
Show answer
Check what the provider value is and what updates it. Likely the value is a new object each render, or it includes fast-changing data such as a token refresh timer or a clock. Memoise the value with useMemo, move frequently changing data out of the context, and split it into separate contexts, so components reading only the user do not re-render when unrelated fields change.
18

How would you implement an optimistic update with TanStack Query and roll back on failure?

Save to ▾
Show answer
In useMutation, use onMutate to cancel ongoing queries, save the previous cache data and update the cache with the expected result. In onError, restore the saved snapshot and show an error toast. In onSettled, invalidate the query so the cache syncs with the server. This makes actions like toggling a favourite feel instant while keeping data correct if the server rejects the change.
19

You inherit an old Redux codebase with connect, hand-written action types and switch reducers. How would you modernise it?

Save to ▾
Show answer
I would migrate gradually, not in one big rewrite. First replace createStore with configureStore, which keeps old reducers working. Then convert one feature at a time to createSlice, switch components from connect to useSelector and useDispatch, and move data fetching to RTK Query. Add tests around each feature before migrating, and remove dead actions. Each step ships independently and reduces risk.
20

A 60-field insurance form becomes laggy while typing because every keystroke re-renders the whole form. How would you fix it?

Save to ▾
Show answer
The state for all fields probably lives in one top-level useState, so each change re-renders every input. Options: switch to React Hook Form so inputs are uncontrolled and only error messages re-render, split the form into memoised sections that own their own state, and debounce expensive validation or derived calculations. Profile first with React DevTools to confirm which components take the most time.
21

Users lose their typed data when they accidentally click another menu link. How would you warn them about unsaved changes?

Save to ▾
Show answer
Track a dirty flag, for example formState.isDirty from React Hook Form. For in-app navigation, use React Router's useBlocker to block the transition and show a confirm dialog. For tab close or refresh, add a beforeunload listener while the form is dirty, and remove it in the effect cleanup. Also consider autosaving drafts, which is often a better user experience than blocking.
22

Typing in a search box feels laggy because a big product list is filtered on every keystroke. How do you fix it?

Save to ▾
Show answer
Keep the input state urgent and make the expensive part deferred. Use const deferredQuery = useDeferredValue(query) and filter with deferredQuery inside useMemo, and wrap the list in React.memo. The input updates instantly while React renders the list in the background and can interrupt it. If filtering is still heavy, debounce it, move it to a Web Worker or filter on the server.
23

A teammate wraps every function in useCallback and every value in useMemo for performance. When should you not memoise, and what feedback would you give?

Save to ▾
Show answer
Memoisation has costs: extra memory, comparisons on every render and more complex code. Skip it for cheap calculations, components that rarely re-render, and props that change every render anyway. Memoising everything by default also hides design problems like state placed too high. Use the profiler to find real bottlenecks first. With the React Compiler, manual memoisation is mostly needed only as an escape hatch.
24

Without using memo, what structural techniques reduce unnecessary re-renders?

Save to ▾
Show answer
Move state down into the smallest component that needs it, so changes do not re-render siblings. Lift content up by passing expensive parts as children: a component that changes state does not re-render JSX it receives as children, because that JSX was created by the parent. Split large components, and avoid putting fast-changing values in widely used context. These often fix performance more cleanly than memoisation.
25

After navigating between pages for a while, the app's memory keeps growing and it slows down. How do you investigate a memory leak?

Save to ▾
Show answer
Take heap snapshots in Chrome DevTools Memory panel before and after repeated navigation and compare retained objects, especially detached DOM nodes. Common React causes are effects without cleanup: intervals, event listeners on window, WebSocket subscriptions and third-party chart instances not destroyed. Also check global caches growing without limits. Add cleanups in useEffect returns and verify the heap stabilises.
26

When would you use a Web Worker in a React application?

Save to ▾
Show answer
Use a Web Worker for CPU-heavy work that would block the main thread, such as parsing a large CSV, image processing, complex calculations or searching big datasets in the browser. The worker runs in a separate thread and communicates through postMessage, so the UI stays responsive. Libraries like Comlink simplify the messaging. Workers cannot touch the DOM, so results are sent back and set in state.
27

How do Suspense and streaming server rendering improve performance?

Save to ▾
Show answer
With streaming SSR, the server sends HTML in chunks: the page shell is sent immediately and slower sections wrapped in Suspense are streamed later as their data resolves, replacing the fallback. Users see content sooner and hydration of parts can happen selectively, prioritising what the user interacts with. Frameworks like Next.js App Router use this with server components and loading.js boundaries.
28

What resource preloading APIs did React 19 add?

Save to ▾
Show answer
React 19 added functions in react-dom such as preload, preinit, prefetchDNS and preconnect. They tell the browser to load fonts, stylesheets, scripts or connect to API origins early, and React places the hints in the document head, including during server rendering. React 19 also supports rendering <title>, <meta> and <link> tags inside components, hoisting them to the head automatically.
29

Field data shows poor INP on the 'Add to cart' button of a product page. How do you improve it?

Save to ▾
Show answer
Profile the click to see what runs. Typical problems are a large synchronous re-render of the whole page, heavy analytics code and expensive state updates in the handler. Update the button feedback first and wrap the non-urgent part (cart drawer, recommendations) in startTransition, defer analytics with setTimeout or requestIdleCallback, narrow which components subscribe to cart state and break up long tasks.
30

After a release, the Lighthouse performance score of the home page dropped from 90 to 60. How do you find and fix the cause?

Save to ▾
Show answer
Compare the two builds instead of guessing. Run Lighthouse and WebPageTest on both versions, check which metric dropped (LCP, TBT or CLS), and diff bundle analyser reports to spot new heavy dependencies. Review the release changes: a new third-party script, an unoptimised hero image or a component moved from server to client are common causes. Fix it, then add a performance budget check in CI to prevent repeats.
31

How do you test a component that depends on Redux and React Router?

Save to ▾
Show answer
Create a custom render helper that wraps the UI with real providers. function renderWithProviders(ui, { route = '/', preloadedState } = {}) { const store = configureStore({ reducer: rootReducer, preloadedState }); return render( <Provider store={store}> <MemoryRouter initialEntries={[route]}>{ui}</MemoryRouter> </Provider> ); } Using a real store with preloaded state is better than mocking useSelector, because it tests actual behaviour.
32

Your tests pass but print 'An update to Component inside a test was not wrapped in act(...)'. What does it mean and how do you fix it?

Save to ▾
Show answer
It means state changed after the test finished checking, usually because an async operation like a fetch or timer resolved later. The test may not be checking the final UI. Fix it by awaiting the result: use findBy queries or waitFor for the expected content, await user-event calls, and use fake timers with act when testing timeouts. Do not just wrap random code in act to hide the warning.
33

In a Next.js app you see a hydration mismatch error. How do you debug it?

Save to ▾
Show answer
It means the HTML rendered on the server differs from the first client render. Common causes: using Date.now(), Math.random() or locale formatting in render, reading window or localStorage during render, browser extensions changing the DOM, and invalid HTML nesting like a div inside a p. Read the diff in the error, render client-only values inside useEffect, use useId for ids and fix invalid markup.
34

A test in CI fails randomly about one time in ten. How do you handle a flaky test?

Save to ▾
Show answer
First reproduce it by running the test repeatedly and in random order. Common causes are shared state between tests, missing awaits, real timers or dates, network calls not mocked and tests depending on execution order. Fix the root cause: reset mocks and stores in afterEach, use findBy or waitFor instead of fixed sleeps, freeze time with fake timers. Quarantine it with a ticket only temporarily, never ignore it.
35

Explain the testing pyramid and the testing trophy. Which applies to frontend?

Save to ▾
Show answer
The pyramid says write many unit tests, fewer integration tests and very few end-to-end tests, because lower tests are fast and cheap. The testing trophy, popular for frontend, adds static analysis at the base and puts the most effort in integration tests, since they give the best confidence per cost. For React, I rely on TypeScript and ESLint, many component tests, some unit tests for logic and a few E2E tests.
36

How do you track errors that happen in production React apps?

Save to ▾
Show answer
Use an error monitoring tool such as Sentry, which captures exceptions with stack traces, browser and user context. Upload source maps during the build so minified stack traces map back to your source, but do not serve them publicly. Wrap major sections in error boundaries that report errors and show friendly fallbacks. Add release tags so you can see which deployment introduced a bug.
37

What are React Server Components?

Save to ▾
Show answer
Server Components run only on the server or at build time. They can be async, read databases or files directly and send rendered output to the client without shipping their JavaScript, which reduces bundle size. They cannot use state, effects or browser APIs. Client Components, marked with 'use client', handle interactivity. Server Components can render Client Components and pass them serialisable props.
38

How does caching work in modern Next.js?

Save to ▾
Show answer
Next.js can cache rendered routes and data. In Next.js 15 and later, fetch requests and GET route handlers are not cached by default; you opt in with options like next: { revalidate: 3600 } or the 'use cache' directive with Cache Components in newer versions. You invalidate with revalidatePath or revalidateTag after mutations. The router also caches visited pages on the client. Understanding what is static versus dynamic avoids stale data bugs.
39

What is middleware, now called proxy, in Next.js?

Save to ▾
Show answer
It is code that runs before a request completes, at the edge of the app, and can rewrite, redirect, set headers or cookies based on the request. In Next.js 16 the middleware.ts file convention was renamed to proxy.ts, with the same idea. Typical uses are redirecting unauthenticated users, locale detection, A/B testing and bot filtering. Keep it light, and still check authorisation in data access code.
40

An e-commerce site has one lakh product pages whose prices change a few times a day. Which rendering strategy would you choose in Next.js?

Save to ▾
Show answer
Building all pages statically at deploy time would be slow, and full SSR on every request adds server load. I would pre-render the most popular products at build time, generate the rest on first request and cache them, and revalidate on demand with revalidateTag when prices change in the admin. Stock and price shown in the cart would be fetched fresh. This gives static speed with correct data.
41

What are micro-frontends and when are they worth it?

Save to ▾
Show answer
Micro-frontends split a large web app into independently built and deployed parts owned by different teams, for example search, cart and account, combined at runtime using Module Federation, iframes or route-level composition. They help very large organisations scale teams and deployments. The costs are duplicated dependencies, harder shared state, inconsistent UX and complex tooling, so they are rarely worth it for small or medium teams.
42

How would you build and maintain a shared component library for several React apps?

Save to ▾
Show answer
Start with design tokens for colours, spacing and typography, and build accessible primitives, often on top of headless libraries like Radix UI or React Aria. Document components in Storybook with usage examples, add visual regression and accessibility tests, and publish versioned packages from a monorepo with semantic versioning and changelogs. Keep APIs small and composable, and collect feedback from consuming teams before breaking changes.
43

How do you write a generic, type-safe List component in TypeScript?

Save to ▾
Show answer
Use a type parameter so the item type flows to the render callback. type ListProps<T> = { items: T[]; getKey: (item: T) => string | number; renderItem: (item: T) => React.ReactNode; }; function List<T>({ items, getKey, renderItem }: ListProps<T>) { return <ul>{items.map(i => <li key={getKey(i)}>{renderItem(i)}</li>)}</ul>; } <List items={users} getKey={u => u.id} renderItem={u => u.name} />
44

Describe how you led a large frontend migration.

Save to ▾
Show answer
Situation: our app ran on an unmaintained Create React App setup with slow builds. Task: move to Vite without stopping feature work. Action: I wrote a migration plan, fixed incompatible imports and env variables in a branch, ran both builds in CI for a week, and trained the team on differences. Result: we switched over a weekend, local start time went from over a minute to a few seconds, and nothing broke for users.

Frequently asked questions

Who should practise hard React JS questions?

Candidates targeting senior roles or top product companies, and anyone who finds the medium set easy.

How should I practise these?

Answer each question aloud before opening the answer, mark it as solved only when your answer matches the key idea, and save misses to Last-Minute Revision.

Other difficulty levels