76 State Management interview questions with detailed answers, sorted from basic to advanced. Free to practice, with progress saved automatically.
State management is how a frontend application organises and synchronises data that changes over time. The questions cover classifying kinds of state, when no library is needed yet, the Context API, Redux Toolkit, Zustand, Jotai, React Query for server data, URL state, selectors, using a store outside React, and structuring state with Immer.
and 6 more topics.
Redux Toolkit (RTK) is the official toolset from the Redux team and the recommended way to write Redux today. Since Redux 4.2, plain Redux's createStore is marked deprecated to push users toward RTK's configureStore. One createSlice replaces three things you write by hand in plain…
createSlice is the Redux Toolkit API that puts three things you used to write separately in one place: action types, action creators and the reducer. You pass name, initialState and a reducers object; it returns slice.actions and slice.reducer. - Generated actions: every key in reducers…
createAsyncThunk creates a thunk action creator for async logic (API calls) and automatically dispatches three lifecycle actions: pending (request started → show loading), fulfilled (success → update data), rejected (failure → show error). Handle it in the slice with extraReducers and the builder pattern: -…
A selector is a function that takes the whole Redux state and returns the piece a component needs, read directly or derived (filtered, counted). createSelector (from Reselect, re-exported by Redux Toolkit) creates a memoized selector: if its inputs haven't changed, it returns the exact previous…
Middleware in Redux is a layer between dispatch(action) and the reducer. Each middleware receives the action, can log, change, delay or block it, and calls next(action) to pass it on. The reducer sits at the end of the chain. Middleware is three nested functions. The…
useSelector reads a piece of state from the Redux store and subscribes to it; useDispatch returns the dispatch function for sending actions. After every action, useSelector reruns the selector and compares the new result with the previous one using === (reference equality). Different means the…
Zustand is a React state library with a very small API: you call create once and get a hook back. State and the functions that update it are declared together, with no action types, reducers or dispatch, and no Provider wrapping the app. set merges…
Use create from zustand, passing an initializer that receives (set, get) and returns an object holding both state and actions in one place. set() merges partial state at the top level, so you do not spread the whole state, and get() reads the current state…
Actions in Zustand are plain functions living next to state in the store object — no action types, no dispatch, no reducers. Three common patterns, written inside a store (the example is JavaScript; with TypeScript use create<Store()(...)): A common slip: set merges only at the…
Two architectures that differ at the root. Redux follows Flux: action object → dispatch → middleware → reducer → new state. Zustand has no notion of action types or reducers — just functions calling set(). Redux today is written with Redux Toolkit: configureStore combines reducers…
Always use a specific selector — calling useStore() with no selector subscribes to the whole store and re-renders on any state change. Zustand compares the selector result with Object.is, so a selector returning a primitive is fine: if the value is unchanged there is no…
Middleware in Zustand is a function that wraps the store creator ((set, get, api) = ({...})) and returns a new store creator, usually to replace set with a version that adds behavior. devtools is the middleware that connects a store to the Redux DevTools extension…
The persist middleware from 'zustand/middleware' serializes state to JSON, saves it to storage, and rehydrates on app startup. - name is the storage key and is required. - partialize picks what to persist — leave out loading/error state. - storage swaps the engine: sessionStorage, or…
React Query (now TanStack Query, currently v5) is a library for managing server state: data that lives on the server, where the app only holds a copy that may be out of date. It handles fetching, caching, re-syncing and updating that data, replacing the hand-written…
useQuery is TanStack Query's hook for reading data. It takes an object with two required options, queryKey and queryFn, and returns the data plus status flags. - queryKey: an array identifying the data in the cache. A different key is a separate cache entry. -…
useMutation handles write operations (POST, PUT, DELETE). Unlike useQuery, which fetches on mount, it only sends the request when you call mutate(), or mutateAsync() if you need to await the result (then try/catch yourself). It also returns isPending, isError, data and error for loading and…
Cache invalidation tells React Query that cached data is out of date and should be fetched again. You call it when you know the data just changed, most often after a successful mutation. invalidateQueries does two things: - Marks matching queries as stale. Matching is…
These two options answer different questions: staleTime decides when data needs a refetch, gcTime decides when data is removed from memory. gcTime is the v5 name for cacheTime. - staleTime (default 0): how long data counts as fresh after the last fetch. While fresh, a…
There are two kinds of state, different in nature: - Client state (theme, sidebar open, form input) — owned entirely by the client, updates land instantly, and it never goes "stale". - Server state (user profile, todo list, product catalog) — owned by the server,…
QueryClient holds the whole cache and config. In the browser you need just one instance, created outside any component so it is not rebuilt on every render. defaultOptions.queries applies to every query unless overridden per query. - The default is 3 retries with exponential backoff;…
refetchOnWindowFocus (default true) makes React Query refetch automatically when the user comes back to the browser tab. The goal is that users never stare at old data: they look at a dashboard, switch to something else for 10 minutes, come back, and the numbers are…
React Query v5 removed onError, onSuccess and onSettled from queries: you can no longer pass them to an individual useQuery, and they are gone from defaultOptions.queries too. Query errors are now handled in two places: the onError callback on QueryCache for shared concerns (toasts, logging),…
QueryKey design is the foundation of cache architecture. Three rules: include EVERY dependency used in queryFn (['todos', userId, { status, page, sort }]); use arrays rather than plain strings, since only arrays support fuzzy invalidation; order them general to specific (['todos'] → ['todos', todoId] →…
Jotai is an atom-based state library: each piece of state is a small atom, and a component only re-renders when the atoms it reads change. Recoil (from Meta) pioneered the same idea; Jotai keeps it with a smaller API and a much smaller bundle. useAtom…
In Jotai, an atom is a small piece of state. You create it with atom() outside components; the atom itself is only a config (an initial value or how to compute it), and the actual value lives in Jotai's store. Components read and write atoms…
Provider-less is the default: with no Provider, Jotai uses a global default store, so every component shares the same atom values without wrapping a Provider. An atom is only a definition; its value lives in a store. A Provider creates a separate store for the…
There is no "best library" answer here. The core difference is how each tool tells components that state changed, because that decides which components re-render. Context API Zustand Redux Toolkit Jotai --------------- Model Passes one value down the tree One store, read through selectors One…
createSlice takes a slice name, an initial state and an object of reducer functions, and generates both the reducer and the matching action creators at once. Thanks to Immer, reducers can be written in a "mutate state directly" style. Three things createSlice does for you:…
QueryClientProvider is the React Context Provider that carries a QueryClient instance down the component tree. The QueryClient is the brain holding the cache, query states and config — every useQuery/useMutation below it needs access. In the Next.js App Router: put the provider in the root…
isLoading is true only during the first load, when the cache has no data yet. isFetching is true whenever a request is running for that query, including background refetches when data already exists. As the user moves around: - First visit: the cache is empty…
Both give a component something to show before the fetch finishes. The difference is whether React Query treats it as real data: - initialData is written to the cache as real data. It has an update time, so it takes part in the staleTime check:…
select is a function that transforms data after it is read from the cache and before it reaches the component. The cache still holds the raw data from queryFn; each component sharing the query can select in its own way. This component only needs the…
Parallel queries means running several queries at once instead of waiting for one to finish before starting the next (a waterfall). Total wait time is the slowest query, not the sum. React Query gives you two ways: - Several useQuery calls: each hook registers its…
queryFn receives a signal (AbortSignal) in its context — pass it into fetch to enable cancellation. When the component unmounts or the query key changes, React Query aborts the signal and the browser drops the in-flight request. Cancellation happens in three situations: - The component…
keepPreviousData (v4) became placeholderData: (prev) = prev in v5: when the query key changes (page 1 → page 2), React Query keeps the previous page on screen instead of clearing it and showing a loading state. The isPlaceholderData flag tells you old data is being…
enabled decides whether a query is allowed to run on its own. It takes a boolean or (since v5) a function that receives the query and returns a boolean. When enabled is false, React Query keeps the query idle: no fetch on mount, no refetch…
Redux is a centralized state management library for JavaScript, usually paired with React. All shared state lives in one store; to change it you dispatch an action, and a reducer (a pure function taking the old state + action and returning the new state) computes…
Redux rests on three principles: 1. Single source of truth (one place that holds the state) — all state lives in one store. That keeps data in sync across components and makes it easy to serialize and restore (save state to localStorage, rehydrate on reload).…
An action is a plain object describing "what just happened" in the app. It must have a type field (a string identifying the event), and the accompanying data usually goes in payload. - type: the domain/eventName convention, like cart/addItem, tells you in DevTools which part…
A reducer is a pure function that takes (state, action) and returns a new state — it must never mutate state directly; it must return a new copy. - Pure function: no side effects, no API calls, no randomness. The same input always gives the…
The store is the central object holding the app's entire state tree, created with configureStore() (RTK) or createStore() (legacy). Three main methods: - getState() returns the current state. - dispatch(action) pushes an action through middleware and into the reducer to update state. - subscribe(listener) registers…
Classify by who owns the data, not by "global vs local". Four common kinds: - Server state — owned by the backend; the client only holds a possibly stale copy: order lists, user profiles. It is asynchronous, can go stale, and needs cache/refetch/invalidate. Tools: React…
Most small and mid-size projects do not need one. Escalate in this order: 1. useState right where it is used. 2. Lift state up to the nearest common parent when two siblings need it. 3. Props / composition — pass JSX through children to avoid…
Context is a mechanism for passing a value down the tree (dependency injection), not a state manager. It stores nothing itself — the state still lives in the Provider's useState/useReducer. Three things it lacks: - No selectors. Every consumer re-renders when the context value changes,…
Keep them in URL query params, not useState or a global store. The URL is where that state belongs, because it describes "what the user is looking at". Concrete benefits: - Shareable: copy the link and a colleague lands on the same filtered result. -…
Cause: the selector returns a brand-new object/array on every call. Zustand compares selector results with Object.is, so a new object never equals the previous one — the component re-renders, the selector runs again, and it loops. Three fixes: The third fix is selecting only what…
A Zustand store is a plain object independent of React, so you can call it anywhere: Note: getState() returns the value at call time and does not react to later changes — never use it during render, only in event handlers, interceptors, and utilities. Testing:…
Because RTK reducers are wrapped by Immer. What you receive is not the real state but a draft proxy: every "mutation" is recorded, then Immer produces a new state copying only the branches that actually changed (structural sharing). The store still receives a brand-new immutable…
Normalizing means storing data as flat tables, like a database: one entities map per entity type, looked up by id, plus an ids array for ordering — instead of keeping the nested JSON tree the API returns. This is also the { ids, entities }…
By default, no. Typed values are local state whose lifetime equals the form's, and they change on every keystroke. Putting them in a global store causes three problems: - Every keystroke dispatches an action → every store subscriber re-runs its selector, and DevTools floods with…
The problem is that the server has no localStorage: reading it directly throws a ReferenceError. If you guard with typeof window, the server renders HTML from defaults while the client's first render uses the stored value. The two differ → React reports a hydration mismatch…
Thunk and Saga are Redux middleware for running side effects (API calls, timers); MobX is an entirely different state library that uses observables instead of an immutable store + actions. So the question really compares two things: async in Redux (Thunk vs Saga), and the…