Skip to content

React JS Technical Round Questions & Answers

40 questions for the technical round stage — 5 easy, 28 medium, 7 hard.

About the technical round round

The technical round is the core of a React JS interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

Tips for the technical round round

  • Say your approach out loud before you start writing.
  • After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question.
  • Pick one project you can explain end to end, including what you would do differently.
  • If you get stuck, explain what you would try next instead of going silent.
Solved — technical roundStart 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

Should you keep related values in one state object or several useState calls?

Save to ▾
Show answer
Group values that always change together, like x and y coordinates, into one object. Keep unrelated values in separate useState calls, which makes updates simpler and avoids accidentally overwriting fields. Avoid contradictory or duplicated state, such as isLoading and isError both being true. When many values change together through clear events, useReducer is often a cleaner choice than a big object.
02

What is Zustand and how do you use it?

Save to ▾
Show answer
Zustand is a small state library with a hook-based API and no Provider. import { create } from 'zustand'; const useCart = create(set => ({ items: [], add: item => set(s => ({ items: [...s.items, item] })), clear: () => set({ items: [] }) })); const count = useCart(s => s.items.length); Components subscribe with selectors, so they re-render only when the selected slice changes.
03

Create a Redux Toolkit slice for a counter.

Save to ▾
Show answer
createSlice generates action creators and the reducer. Immer lets you write mutating syntax safely. import { createSlice } from '@reduxjs/toolkit'; const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: state => { state.value += 1; }, addBy: (state, action) => { state.value += action.payload; } } }); export const { increment, addBy } = counterSlice.actions; export default counterSlice.reducer;
04

What is list virtualisation?

Save to ▾
Show answer
Virtualisation, or windowing, renders only the rows visible in the viewport plus a small buffer, instead of thousands of DOM nodes. As the user scrolls, rows are recycled with new data. Libraries such as TanStack Virtual and react-window implement it. It dramatically cuts render time and memory for long lists, chat histories and data grids, but needs care with variable row heights and browser find-in-page.
05

An orders table renders 10,000 rows and the page freezes on load and scroll. What would you do?

Save to ▾
Show answer
First question whether users need all rows at once: server-side pagination or infinite scroll with cursor-based loading may solve it. If a long scrollable list is required, virtualise it with TanStack Virtual so only around 30 rows exist in the DOM. Also memoise row components, avoid heavy formatting in render by precomputing, and move sorting or filtering off the main thread if it is expensive.
06

When should you choose useReducer over useState? Show a small example.

Save to ▾
Show answer
Use useReducer when state has several related fields or many update types, so logic lives in one pure, testable function. function reducer(state, action) { switch (action.type) { case 'added': return [...state, action.item]; case 'removed': return state.filter(i => i.id !== action.id); default: return state; } } const [cart, dispatch] = useReducer(reducer, []); dispatch({ type: 'added', item });
07

Show a Server Function used to handle a form in Next.js.

Save to ▾
Show answer
A function marked 'use server' runs on the server and can be passed to a form action. // app/actions.ts 'use server'; import { revalidatePath } from 'next/cache'; export async function addTodo(formData: FormData) { const title = String(formData.get('title')); await db.todo.create({ data: { title } }); revalidatePath('/todos'); } // app/todos/page.tsx <form action={addTodo}> <input name="title" required /> <button>Add</button> </form> Always validate input and check authorisation inside the function.
08

What is lazy initialisation in useState and when should you use it?

Save to ▾
Show answer
If the initial value is expensive to compute, pass a function instead of the value. React calls it only on the first render. // runs on every render (wasteful) const [data, setData] = useState(JSON.parse(localStorage.getItem('cart'))); // runs once const [data, setData] = useState(() => JSON.parse(localStorage.getItem('cart') ?? '[]') );
09

What is useId and why not use Math.random for ids?

Save to ▾
Show answer
useId generates a unique, stable id for a component instance that matches between server and client rendering. It is used for accessibility attributes, like linking a label's htmlFor to an input's id. Math.random or a global counter would produce different values on the server and the browser, causing hydration mismatches, and would change on every render. useId should not be used as list keys.
10

What is the difference between useMemo and useCallback?

Save to ▾
Show answer
useMemo caches the result of a calculation between renders, like a filtered list: useMemo(() => filter(items, q), [items, q]). useCallback caches a function definition itself: useCallback(fn, deps), which equals useMemo(() => fn, deps). Both are performance tools, useful when passing values to React.memo children or effect dependencies. With the React Compiler enabled, much of this manual memoisation becomes unnecessary.
11

What is the difference between getBy, queryBy and findBy queries?

Save to ▾
Show answer
getBy returns the element or throws immediately if it is missing, so use it when the element should exist. queryBy returns null instead of throwing, which is the right choice to assert something is not present: expect(screen.queryByText('Error')).not.toBeInTheDocument(). findBy returns a promise and retries until the element appears or times out, used for content that appears after async work. Each has an AllBy variant for multiple elements.
12

How do you test a component that fetches data from an API?

Save to ▾
Show answer
Mock the network, not fetch itself, using Mock Service Worker (MSW), then wait for the UI. const server = setupServer( http.get('/api/users', () => HttpResponse.json([{ id: 1, name: 'Asha' }])) ); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); test('shows users', async () => { render(<Users />); expect(await screen.findByText('Asha')).toBeInTheDocument(); });
13

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.
14

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.
15

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.
16

What is tree shaking?

Save to ▾
Show answer
Tree shaking is dead code elimination done by bundlers like Rollup (used by Vite), esbuild and webpack. They analyse ES module import and export statements and drop exports that are never used. It works only with static ESM syntax, not CommonJS require, and can be blocked by modules with side effects. Importing import { debounce } from 'lodash-es' instead of the whole lodash library keeps the bundle small.
17

Why should you use the functional form of setState? Show an example.

Save to ▾
Show answer
State updates are scheduled, and each render sees a snapshot of state. If you call setCount(count + 1) several times in one handler, all calls read the same old value. The updater function receives the latest pending value. function addThree() { setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); } // count increases by 3, not 1
18

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.
19

How do you set SEO metadata in a Next.js App Router page?

Save to ▾
Show answer
Export a metadata object or a generateMetadata function from a page or layout. export async function generateMetadata({ params }) { const { slug } = await params; const post = await getPost(slug); return { title: post.title, description: post.summary, alternates: { canonical: `/blog/${slug}` }, openGraph: { title: post.title, images: [post.cover] } }; } Next.js renders the tags in the head during server rendering.
20

During a security review you find a payment provider's secret key in the browser's JavaScript bundle. What do you do?

Save to ▾
Show answer
Treat it as leaked: rotate the key immediately in the provider dashboard and check logs for misuse. Then fix the cause, usually the secret was in a public env variable such as NEXT_PUBLIC_ or VITE_, or imported into a client component. Move the call to a server route or Server Function that uses a server-only variable, add the server-only package to guard imports, and add secret scanning in CI.
21

Explain CSR, SSR, SSG and ISR.

Save to ▾
Show answer
CSR (client-side rendering) sends a minimal HTML shell and renders everything in the browser. SSR (server-side rendering) generates HTML on each request, good for personalised or fast-changing pages. SSG (static site generation) builds HTML at build time, fastest for pages that rarely change like blogs. ISR (incremental static regeneration) serves static pages but regenerates them in the background after a time or on demand, mixing speed with freshness.
22

Five components on one page each fetch the same user profile, causing duplicate API calls. How would you fix this?

Save to ▾
Show answer
Lift the data fetching into a shared cache instead of each component fetching on its own. With TanStack Query or RTK Query, all components using the same query key share one request and one cached result, and get updates together. Alternatively, fetch once in a parent or route loader and pass data down. This also removes inconsistent loading spinners across the page.
23

A junior notices that every child component re-renders when the parent's state changes, even when their props did not change. How do you explain this?

Save to ▾
Show answer
By default, when a component renders, React re-renders all of its children, because it cannot know whether they depend on something else. Re-rendering is usually cheap since only real changes reach the DOM. If a child is expensive, wrap it in React.memo and keep its props stable with useCallback or useMemo, or let the React Compiler memoise automatically. Moving state down also helps.
24

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.
25

What is the render props pattern?

Save to ▾
Show answer
Render props means passing a function as a prop that the component calls to decide what to render, for example <MouseTracker render={pos => <Cursor pos={pos} />} />. It lets a component share its state or behaviour while the caller controls the UI. Like HOCs, it has mostly been replaced by custom hooks, but libraries for forms and virtualised lists still expose render-prop or children-as-function APIs.
26

Your React app works locally, but after deployment refreshing /dashboard gives a 404 from the server. Why, and how do you fix it?

Save to ▾
Show answer
The server looks for a real file at /dashboard, which does not exist because routing happens in the browser. Configure the server to return index.html for all unknown paths: a rewrite rule in Nginx (try_files $uri /index.html), a _redirects or rewrites setting on static hosts, or a fallback in Express. Alternatively, use a framework with server routing. HashRouter also avoids it but gives ugly URLs.
27

How do you find what is making a React page slow?

Save to ▾
Show answer
Use the React DevTools Profiler: record an interaction, then check the flame chart for components with long render times and the reason each one re-rendered (enable 'record why each component rendered'). Pair it with the Chrome Performance panel for long tasks, layout and network. Enable highlight updates to spot unnecessary renders. Measure first, fix the biggest cost, then measure again in a production build.
28

Your team's old codebase uses PropTypes and is upgrading to React 19. What would you recommend for checking prop types?

Save to ▾
Show answer
PropTypes checks props at runtime in development and was removed from the React package in React 19, so it now needs the separate prop-types library and is ignored silently. TypeScript checks types at compile time, gives editor autocompletion, and catches errors before code runs. Most new React projects in India and abroad use TypeScript, and many job descriptions now list it as required.
29

An accessibility audit says your signup form fails screen reader checks. What would you fix to make the form accessible?

Save to ▾
Show answer
Link every input to a visible label using htmlFor and id (useId helps), use correct input types, and mark required fields. Show errors as text, connect them with aria-describedby and set aria-invalid on failing fields. Move focus to the first error on submit, keep everything keyboard operable and do not rely on colour alone. Use native button and form elements instead of clickable divs.
30

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.
31

Show how to fetch data with TanStack Query.

Save to ▾
Show answer
TanStack Query caches by query key and handles loading, errors, retries and background refetching. const queryClient = new QueryClient(); // wrap app: <QueryClientProvider client={queryClient}> function Orders({ userId }) { const { data, isPending, error } = useQuery({ queryKey: ['orders', userId], queryFn: () => fetch(`/api/users/${userId}/orders`).then(r => r.json()), staleTime: 60_000 }); if (isPending) return <Spinner />; if (error) return <p>Error</p>; return <OrderList orders={data} />; }
32

A teammate writes user.name = 'Ravi' inside a click handler and the screen does not update. Why?

Save to ▾
Show answer
React decides to re-render when you call the state setter with a new value. Mutating the existing object does not call the setter, and even if you call setUser(user) with the same mutated object, React compares by reference with Object.is, sees no change and may skip rendering. Always create a new object, like setUser({ ...user, name: 'Ravi' }).
33

Your component fetches data in useEffect and the network tab shows thousands of requests in a loop. What could cause it?

Save to ▾
Show answer
Common causes are a missing dependency array, so the effect runs after every render and its setState triggers another render, or a dependency that is a new object or array each render, like options={{ page: 1 }}. Since Object.is compares by reference, the effect always sees a change. Fix by adding correct dependencies, depending on primitive values, or memoising the object with useMemo.
34

Your manager asks you to set up a new React project in 2026 and suggests Create React App. What would you recommend?

Save to ▾
Show answer
The React team deprecated Create React App in 2025. For a single-page app, use Vite (npm create vite@latest, choose React with TypeScript), which gives very fast dev server and builds. For full-stack apps with routing, SSR and server components, the React docs recommend a framework such as Next.js or React Router in framework mode. Choose based on whether you need server rendering and SEO.
35

A teammate writes setCount(count + 1) three times in a click handler and is surprised that the count increases by only one. Explain why.

Save to ▾
Show answer
Inside one render, count is a constant snapshot, say 0. Each call schedules setCount(0 + 1), so all three set the same value 1, and React batches them into one render. State does not change immediately after calling the setter. To increment three times, use the updater form setCount(c => c + 1), which queues functions that each receive the latest pending value.
36

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.
37

What is the difference between server state and client state?

Save to ▾
Show answer
Client state is owned by the browser, like whether a modal is open, form input or theme. Server state lives on a backend and is only cached on the client, like products or orders; it can become stale, be changed by other users and needs refetching. Tools like TanStack Query or RTK Query manage server state, while useState, Context or Zustand handle client state.
38

How do you use React DevTools for debugging?

Save to ▾
Show answer
The Components tab shows the component tree; selecting one shows its props, state, hooks and context values, which you can edit live to test behaviour. You can find which component rendered a DOM node, view the owner chain and jump to source. The Profiler tab records renders and shows why each component re-rendered. Combined with browser breakpoints and console logs, it speeds up most UI debugging.
39

Which queries should you prefer in React Testing Library and why?

Save to ▾
Show answer
Prefer queries accessible to everyone: getByRole (with the name option) first, then getByLabelText for form fields, getByPlaceholderText, getByText and getByDisplayValue. Use getByAltText and getByTitle for images and titles. getByTestId is a last resort when nothing else works. Role-based queries also check accessibility, because if a button cannot be found by role and name, screen reader users will struggle too.
40

How do you implement code splitting with React.lazy and Suspense?

Save to ▾
Show answer
Load a component only when it is needed with a dynamic import. import { lazy, Suspense } from 'react'; const Reports = lazy(() => import('./Reports')); function App() { return ( <Suspense fallback={<Spinner />}> {showReports && <Reports />} </Suspense> ); } The bundler creates a separate chunk for Reports, reducing the initial JavaScript download.

Frequently asked questions

What happens in the React JS technical round round?

The technical round is the core of a React JS interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

How do I clear the technical round round?

Say your approach out loud before you start writing. After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question. Pick one project you can explain end to end, including what you would do differently.

Which round comes next?

A typical order is online assessment, telephonic screening, one or two technical rounds, a managerial round and HR. Use the other round pages to practise each stage.