React JS Interview Questions for 3 Years Experience
40 questions with answers for candidates with 3 years of experience — 2 easy, 33 medium, 5 hard.
What interviewers expect: 3 years experience
With three years, you are expected to own modules. Interviews include performance profiling, code splitting, Core Web Vitals, Next.js rendering strategies and testing strategy. Expect scenario questions about slow pages, flaky tests and API design, and some frontend system design, for example designing an autocomplete or a product listing page with filters.
This set is weighted towards Performance Optimization (9), Next.js and Advanced React (7), Props and State (6), State Management (5).
Performance optimisation Next.js SSR, SSG and ISR Frontend system design basics Code review and quality
Solved at this levelStart 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.
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.
02
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?
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.
03
How do you type props for a component in TypeScript?
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.
05
Tell me about a time a requirement changed late in a sprint. How did you handle it?
Situation: two days before release, the product owner asked to add a GST number field with validation to our checkout form. Task: deliver it without risking the release. Action: I estimated the impact, proposed shipping the field with basic format validation first and the full verification API in the next sprint, and added tests. Result: we released on time and the follow-up shipped a week later.
In React 19 you can pass a function to a form's action prop: <form action={saveAddress}>. React calls it with the FormData when the form submits, runs it inside a transition, tracks pending state and resets uncontrolled fields after success. Combined with useActionState for results and useFormStatus for button state, this removes manual onSubmit, preventDefault and loading flags. In Next.js the action can be a Server Function.
07
Typing in a search box feels laggy because a big product list is filtered on every keystroke. How do you fix it?
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.
08
A developer sends an analytics event inside a useEffect that watches a 'submitted' state flag. Is this a good pattern?
No. Logic caused by a specific user action belongs in the event handler, not in an effect. Setting a flag and reacting in an effect adds an extra render, is harder to follow and can fire again on remount in StrictMode. Call sendAnalytics directly in handleSubmit. Effects are for synchronising with external systems because the component is displayed, not for responding to events.
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.
10
How do you handle async API calls with createAsyncThunk?
createAsyncThunk creates pending, fulfilled and rejected actions around an async function, and you handle them in extraReducers.
export const fetchOrders = createAsyncThunk('orders/fetch', async () => {
const res = await fetch('/api/orders');
return res.json();
});
extraReducers: builder => {
builder
.addCase(fetchOrders.pending, s => { s.status = 'loading'; })
.addCase(fetchOrders.fulfilled, (s, a) => { s.status = 'idle'; s.list = a.payload; })
.addCase(fetchOrders.rejected, (s, a) => { s.status = 'failed'; s.error = a.error.message; });
}
11
A cart page keeps the selected item object in state and also the items array. After editing the item's quantity, the details panel shows the old quantity. Why?
The selected item is a copy duplicated from the array, so updating the array does not update the copy. This breaks the single source of truth. Store only selectedId in state and derive the item during render: const selected = items.find(i => i.id === selectedId). Now there is one place where each item lives, and every view stays in sync automatically.
12
A teammate writes setCount(count + 1) three times in a click handler and is surprised that the count increases by only one. Explain why.
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.
13
Tell me about working with a backend team when an API did not match what the frontend needed.
Situation: the orders API returned deeply nested data and no pagination, making our list screen slow. Task: get a better contract without blocking either team. Action: I shared a proposed response shape with the backend developer, explained the performance impact with numbers and built against a mocked API using MSW meanwhile. Result: they added pagination and flatter fields, and we integrated in two days with no rework.
The React Compiler, released as stable version 1.0 in late 2025, is a build-time tool (Babel plugin, also integrated in Vite and Next.js setups) that automatically memoises components and values. It analyses your code and inserts caching similar to useMemo, useCallback and React.memo, so developers write plain code. It requires following the rules of React, such as pure rendering, and can be adopted gradually per directory.
15
Show a Server Function used to handle a form in Next.js.
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.
16
What is the difference between the App Router and the Pages Router in Next.js?
The Pages Router uses the pages directory with getServerSideProps and getStaticProps for data. The App Router, the recommended default, uses the app directory with nested layouts, React Server Components by default, streaming with loading.js, error.js boundaries and Server Functions. Data is fetched directly in async server components. Many older projects still use Pages Router, and both can coexist during migration.
17
A timer using setInterval inside useEffect always shows 1 and never goes higher. What is the bug?
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.
18
What are memoised selectors and why use createSelector?
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.
19
A new mid-sized dashboard project is starting. How would you decide between Context, Redux Toolkit and Zustand?
First separate server state, which I would handle with TanStack Query or RTK Query. For remaining client state, local useState covers most needs. Context suits rarely changing values like theme or auth. If there is complex shared state with many actions, strict patterns, large teams and need for DevTools time travel, Redux Toolkit fits. For simpler shared state with less boilerplate, Zustand is a good choice.
20
In development your API call inside useEffect fires twice, but only once in production. Why does this happen, and what does StrictMode do?
StrictMode is a development-only wrapper that helps find bugs. It renders components twice to catch impure rendering, and mounts, unmounts and remounts components to check that effects clean up properly. This is why a fetch in useEffect may appear to fire twice locally. It does not happen in production. The fix is to write proper cleanup, not to remove StrictMode.
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.
22
You wrapped a row component in React.memo, but the profiler shows it still re-renders every time. The parent passes onClick={() => remove(id)}. Why?
An inline function like onClick={() => remove(id)} creates a new function object on every render. React.memo compares props by reference, sees a different function and re-renders the child every time, so memo has no effect. Wrap the handler with useCallback and correct dependencies, or let the React Compiler handle it. If the child is not memoised, inline functions are fine and cheap.
Use vi.mock with a factory, then control return values per test.
import { vi } from 'vitest';
import { getProfile } from './api';
vi.mock('./api', () => ({
getProfile: vi.fn()
}));
test('shows name', async () => {
getProfile.mockResolvedValue({ name: 'Ravi' });
render(<Profile />);
expect(await screen.findByText('Ravi')).toBeInTheDocument();
});
In Jest, the same pattern uses jest.mock and jest.fn.
24
Your team uses the array index as key in a todo list and users report that checkboxes get ticked on the wrong item after deleting a row. What is happening?
When an item is deleted, every later item shifts to a new index, so React matches old state and DOM nodes to the wrong items. Uncontrolled inputs and component state stay attached to the index, not the data. The fix is to use a stable unique id from the data as the key. Index keys are only safe for static lists that never reorder, filter or delete.
25
How do you test a component that fetches data from an API?
Options include plain CSS or CSS Modules (scoped class names), Tailwind CSS utility classes, Sass, and CSS-in-JS libraries like styled-components or Emotion. Runtime CSS-in-JS needs client-side JavaScript and context, so it fits poorly with Server Components. That is why Tailwind and CSS Modules, or zero-runtime libraries, are the most common choice in modern Next.js projects. Component libraries like shadcn/ui build on Tailwind.
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.
29
What is the difference between server state and client state?
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.
'use client' at the top of a file marks a boundary: that module and everything it imports become part of the client bundle and can use state, effects, event handlers and browser APIs. It does not mean the component is only rendered on the client; it is still pre-rendered to HTML on the server and then hydrated. Keep 'use client' as low in the tree as possible to send less JavaScript.
31
How do you track errors that happen in production React apps?
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.
32
How do you create a protected route that redirects users who are not logged in?
Create a wrapper that checks auth and either renders the child routes or redirects.
import { Navigate, Outlet, useLocation } from 'react-router';
function RequireAuth() {
const { user } = useAuth();
const location = useLocation();
if (!user) return <Navigate to="/login" replace state={{ from: location }} />;
return <Outlet />;
}
<Route element={<RequireAuth />}>
<Route path="/account" element={<Account />} />
</Route>
The server must still enforce authorisation.
33
How would you explain to a non-technical manager why our marketing pages need server rendering?
I would keep it simple: a pure client-side React page sends an almost empty HTML file, so some crawlers and social previews see little content and users on slow phones see a blank screen longer. Server rendering sends ready HTML, improving SEO and first load. I would back this with a Lighthouse comparison and search console data, and suggest a framework like Next.js only for pages that need it.
34
How do you implement code splitting with React.lazy and Suspense?
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.
35
What are Core Web Vitals and how do React apps affect them?
Core Web Vitals are Google's user experience metrics: LCP (Largest Contentful Paint, loading, target under 2.5s), INP (Interaction to Next Paint, responsiveness, under 200ms, which replaced FID in 2024) and CLS (Cumulative Layout Shift, visual stability, under 0.1). Large React bundles hurt LCP and INP, heavy renders on clicks hurt INP, and images or content without reserved space hurt CLS. SSR, code splitting and transitions help.
I enjoy building things people use directly, and React lets me turn designs into fast, interactive screens. During my training I built three projects with React, TypeScript and APIs, and I liked how components make code reusable. React also has strong demand across startups and service companies in India, a large community and paths into Next.js and React Native, so I see long-term growth in this role.
37
A developer stores fullName in state and updates it in a useEffect whenever firstName or lastName changes. What would you suggest?
This is redundant derived state. It causes an extra render and risks the values going out of sync. Compute it during render instead: const fullName = firstName + ' ' + lastName. If the calculation is expensive, wrap it in useMemo. As a rule, if a value can be calculated from existing props or state, do not store it in state and do not sync it with an effect.
38
During a security review you find a payment provider's secret key in the browser's JavaScript bundle. What do you do?
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.
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.
Use renderHook from React Testing Library and wrap state changes in act.
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
test('increments', () => {
const { result } = renderHook(() => useCounter(5));
act(() => result.current.increment());
expect(result.current.count).toBe(6);
});
If the hook needs context, pass a wrapper option with the provider.
Frequently asked questions
What is asked in React JS interviews for 3 years of experience?
With three years, you are expected to own modules. Interviews include performance profiling, code splitting, Core Web Vitals, Next.js rendering strategies and testing strategy. Expect scenario questions about slow pages, flaky tests and API design, and some frontend system design, for example designing an autocomplete or a product listing page with filters.
How many questions should I practise before a React JS interview?
Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.
Which topics matter most for 3 years of experience?
Focus first on Performance optimisation, Next.js SSR, SSG and ISR, Frontend system design basics, Code review and quality.