Skip to content

State Management Interview Questions for React JS

25 State Management questions with answers for react js roles — 6 easy, 13 medium, 6 hard.

About State Management

Real apps share state across many screens. This topic explains Context and its performance pitfalls, Redux core ideas, Redux Toolkit slices, thunks and RTK Query, memoised selectors and normalised state, plus lighter options like Zustand and Jotai, and the key difference between client state and server state handled by TanStack Query.

Sheet progressStart 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 the Context API and when should you use it?

Save to ▾
Show answer
Context lets a parent provide a value to any component below it without passing props through every level. You create it with createContext, provide a value high in the tree and read it with useContext. It is ideal for values that many components need and that change rarely, such as theme, language, logged-in user or feature flags. It is not a full replacement for a state management library.
02

Explain the core concepts of Redux.

Save to ▾
Show answer
Redux keeps global state in a single store. The UI dispatches actions, which are plain objects describing what happened, like { type: 'cart/added', payload }. Reducers are pure functions that take the current state and an action and return new state. Components read state with selectors. This strict one-way flow makes state changes predictable, traceable in DevTools and easy to test.
03

How do you set up a Redux store and use it in components?

Save to ▾
Show answer
Use configureStore, wrap the app with Provider and use hooks. const store = configureStore({ reducer: { counter: counterReducer } }); createRoot(el).render( <Provider store={store}><App /></Provider> ); function Counter() { const value = useSelector(state => state.counter.value); const dispatch = useDispatch(); return <button onClick={() => dispatch(increment())}>{value}</button>; }
04

Why must Redux reducers be pure functions?

Save to ▾
Show answer
A pure reducer returns new state based only on its inputs and has no side effects: no API calls, no random values, no mutation of arguments. This makes state changes predictable, testable and replayable, which is what enables time-travel debugging in Redux DevTools. Side effects belong in thunks, listeners or RTK Query. Redux Toolkit uses Immer, so draft mutations inside createSlice are still safe.
05

How do you debug Redux state changes?

Save to ▾
Show answer
Install the Redux DevTools browser extension; configureStore enables it automatically in development. You can see every dispatched action, the state before and after, the diff, and jump back in time to earlier states. Check whether the expected action was dispatched, whether its payload is correct and whether the reducer updated the right slice. For selectors, log their output or use the React DevTools profiler.
06

What is the Flux architecture?

Save to ▾
Show answer
Flux is the unidirectional data flow pattern that inspired Redux. Actions describe events, a dispatcher sends them to stores, stores update data and notify views, and views render and trigger new actions. Data never flows backward directly. Redux simplified Flux into a single store with pure reducers. Understanding Flux explains why React state management favours one-way flow over two-way binding.
07

What is the main performance pitfall of React Context and how do you avoid it?

Save to ▾
Show answer
Every component that reads a context re-renders when the provider value changes. If you pass a new object like value={{ user, setUser }} on each render, all consumers re-render even if nothing changed. Memoise the value with useMemo, split one big context into smaller ones (for example state and dispatch separately), and keep fast-changing data in a dedicated store like Zustand that supports selectors.
08

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;
09

How do you handle async API calls with createAsyncThunk?

Save to ▾
Show answer
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; }); }
10

What is RTK Query?

Save to ▾
Show answer
RTK Query is the data fetching and caching tool included in Redux Toolkit. You define an API with createApi and endpoints, and it auto-generates hooks like useGetProductsQuery and useAddProductMutation. It handles loading states, caching, deduplicating requests, refetching and cache invalidation through tags. It removes most hand-written thunks and reducers for server data in Redux-based apps.
11

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

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} />; }
13

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

A new mid-sized dashboard project is starting. How would you decide between Context, Redux Toolkit and Zustand?

Save to ▾
Show answer
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.
15

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

How do you persist part of your state, like the cart, across page reloads?

Save to ▾
Show answer
Save it to localStorage and read it on startup. With Zustand, the persist middleware does this. import { persist } from 'zustand/middleware'; const useCart = create(persist( set => ({ items: [], add: i => set(s => ({ items: [...s.items, i] })) }), { name: 'cart-storage' } )); With Redux, use redux-persist or a store.subscribe that writes a throttled snapshot. Never store secrets like tokens in localStorage.
17

Where should you store the logged-in user's auth token in a React app?

Save to ▾
Show answer
The safest common choice is an httpOnly, Secure, SameSite cookie set by the server, because JavaScript cannot read it, which protects it from XSS theft. Keep only non-sensitive user info, such as name and role, in React state or context. If you must use tokens in JavaScript, keep the access token in memory, use short expiry and refresh tokens via cookies. Avoid localStorage for tokens.
18

What is atomic state management, as in Jotai or Recoil-style libraries?

Save to ▾
Show answer
Atomic state splits state into small independent units called atoms. Components subscribe to only the atoms they use, and derived atoms compute values from others, similar to a spreadsheet. This gives fine-grained re-renders without selectors or a big central store. Jotai is the popular option today; Recoil is no longer maintained. It suits apps with many independent pieces of interactive state.
19

Tell me about a time you pushed back on adding a new library to the project.

Save to ▾
Show answer
Situation: a teammate proposed adding Redux to a small admin app. Task: decide whether it was worth the added complexity. Action: I listed our actual shared state, which was just the user and a few filters, and showed a prototype using TanStack Query for API data and one small context. Result: we shipped faster with less code, and agreed to revisit if requirements grew.
20

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

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

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

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

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

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.

Frequently asked questions

How important is State Management in React JS interviews?

Real apps share state across many screens. This topic explains Context and its performance pitfalls, Redux core ideas, Redux Toolkit slices, thunks and RTK Query, memoised selectors and normalised state, plus lighter options like Zustand and Jotai, and the key difference between client state and server state handled by TanStack Query.

How should I use the State Management sheet?

Go top to bottom — easy to hard. Answer before opening each answer, tick what you solved and save misses to Last-Minute Revision.