State Management
The libraries that hold state outside components — single stores, atoms, observables — and the trade-offs between them. Interviewers ask because deciding where state lives and how it is derived is what keeps a growing frontend codebase changeable.
on this pageshowhide
explore
- Redux (has its own guide)30 questions
- Store and Data Flow5 questions
- Actions and Reducers5 questions
- Middleware and Thunks5 questions
- Selectors and Reselect4 questions
- Redux Toolkit6 questions
- React Integration and DevTools5 questions
- RTK Query5 questions
- Zustand6 questions
- Jotai5 questions
- MobX5 questions
- NgRx45 questions
- Store Setup & Actions6 questions
- Reducers & Feature Slices5 questions
- Selector Memoization5 questions
- Action-Driven Effects6 questions
- Signal Store Anatomy6 questions
- Signal Store Extensions6 questions
- Entity Adapters & Local Stores5 questions
- Mock Stores & Effect Tests6 questions
- NGXS5 questions
- RxJS (has its own guide)56 questions
- Observables and Observers5 questions
- Stream Creation Functions5 questions
- Pipeable Operators25 questions
- Subjects and Multicasting5 questions
- Catching Errors & Retrying5 questions
- Unsubscribing & Schedulers6 questions
- Marble Diagram Tests5 questions
- Pinia41 questions
- Defining Stores5 questions
- Patching & Subscribing6 questions
- Store Getters5 questions
- Actions & Action Hooks5 questions
- Store Plugins5 questions
- SSR & Non-Component Use5 questions
- Unit-Testing Store Logic5 questions
- Moving From Vuex5 questions
- VueUse5 questions
→ has its own guide
questions
203 · 10 sectionsIn Redux, what is an action, what is an action creator, and why write action creators instead of inline action objects?
basics
~20 sAn action is a plain object with a string type that describes something that happened, usually with its data in payload; an action creator is a function that builds that object, so every dispatch site produces the same, correctly shaped action.
Why can't a plain Redux store run async logic on its own, and how does redux-thunk let you dispatch an async fetch?
basics
~20 sThe core dispatch accepts only plain objects and reducers must stay synchronous and pure, so async work needs middleware; redux-thunk lets you dispatch a function that receives dispatch and getState and dispatches plain actions as the request progresses.
In React-Redux, what do Provider, useSelector and useDispatch each do when you connect a Redux store to a React app?
basics
~20 sProvider makes the Redux store available to every component beneath it. useSelector reads a value from the store and re-renders the component when that value changes. useDispatch returns the store's dispatch function so components can send actions.
In Redux, what is a selector function, and why do components call selectors instead of reading state paths directly?
basics
~20 sA Redux selector is a function of the root state, plus optional arguments, returning part of it or a value derived from it. Using selectors keeps knowledge of the state shape in one place, so reshaping state changes selectors, not components.
In a React app backed by Redux, what happens, step by step, from a user clicking an Add button until the screen shows the change?
basics
~20 sThe click handler dispatches an action; the store runs the root reducer with the current state and that action, saves the result, then calls every subscriber, and the subscribed UI rereads state and re-renders what changed.
In RTK Query, which hooks does createApi generate for a getPosts query and an updatePost mutation, and how do isLoading and isFetching differ?
basics
~20 sRTK Query's React createApi generates useGetPostsQuery and useLazyGetPostsQuery for the query and useUpdatePostMutation for the mutation. isLoading is true only while the hook has no data yet; isFetching is true whenever a request is in flight, including refetches.
In RTK Query, how do providesTags and invalidatesTags refresh a posts list and a post detail after updatePost, and why add a 'LIST' id?
basics
~20 sIn RTK Query, queries declare providesTags and mutations declare invalidatesTags; after a mutation, subscribed queries with a matching tag refetch. Tag each post { type: 'Post', id } and the list also with id 'LIST', so edits and additions refetch only what they affect.
In RTK Query, when does a cached query result get reused, refetched or removed, given keepUnusedDataFor and refetchOnMountOrArgChange?
basics
~20 sRTK Query reuses a cached result while any subscriber holds it and does not refetch on mount by default. keepUnusedDataFor (60 seconds) removes it after the last subscriber leaves; refetchOnMountOrArgChange forces or age-limits refetching on subscribe.
In RTK Query, what must you add to configureStore for an api slice, and what breaks if the middleware or setupListeners is missing?
basics
~20 sAn RTK Query api slice needs its reducer mounted at api.reducerPath and api.middleware appended to the store. Without the middleware, cache lifetimes, invalidation and polling do not run; without setupListeners, refetchOnFocus and refetchOnReconnect never fire.
In RTK Query, how do you make an updatePost mutation update the cached post optimistically and roll back if the request fails?
basics
~20 sIn RTK Query, give the mutation an onQueryStarted callback that dispatches api.util.updateQueryData to patch the cached post, keeps the returned patchResult, awaits queryFulfilled, and calls patchResult.undo() if it rejects. Invalidating tags on failure is the safer rollback when edits overlap.
With Zustand 5, how do you create a store, read it from a component, and update it without wrapping the app in a Provider?
basics
~20 sZustand's create((set, get) => ({...})) returns a hook bound to a module-level store. Components call that hook with a selector to read a slice, and actions defined inside the store call set to update it, so no Provider is needed.
In Zustand 5, how do you read several fields from a store in one hook call without extra re-renders, and what replaced v4's equality-function argument?
basics
~20 sWrap the selector in useShallow from zustand/shallow: it returns the previous object or array when the new one is shallow-equal, so the hook sees a stable reference. v5 dropped the hook's equality argument; createWithEqualityFn in zustand/traditional keeps the old style.
In Zustand, what does set() do with the object you pass it, and when would you pass true as its second argument?
basics
~20 sset shallow-merges the object into a copy of the current state: omitted top-level keys survive, but nested objects you pass replace the old ones wholesale. Passing true as the second argument replaces the state outright, used for resets or removing keys.
With Zustand 5, how do you give each server request, test or widget its own store instance instead of the single module-level store that create builds?
basics
~20 sBuild the store with a factory around createStore, create one instance per provider mount with useState(() => createCartStore(initial)), pass that instance through React Context, and read it with useStore(store, selector). Each request, test or widget then owns an isolated store.
A Zustand cart store wrapped in persist gains a nested shipping.method field, and returning users see it as undefined after the release; why, and how do you fix it?
basics
~20 spersist rehydrates with a shallow merge, so the stored shipping object replaces the new default one and method never appears. Bump the persist version and add a migrate function that fills the field, or supply a deep merge.
In Jotai, what are primitive, read-only derived and writable derived atoms, and which hook do you use to read or write each?
basics
~20 satom(initial) makes a primitive atom; atom(get => ...) a read-only derived atom; atom(read, write) a writable derived one, with atom(null, write) as a write-only action. useAtom returns [value, set], useAtomValue the value, useSetAtom only the setter.
In Jotai, what changes when you wrap part of the tree in a Provider, and how do createStore and getDefaultStore relate to it?
basics
~20 sA Provider gives its subtree its own store, so the same atoms hold separate values there; without one, hooks use the default store from getDefaultStore. createStore makes a store to pass to Provider and to use outside React.
When one form field changes, how does Jotai decide what recomputes and re-renders, compared with a single Zustand store holding all the fields?
basics
~20 sJotai follows the dependency graph recorded by get: writing one field atom recomputes only the mounted atoms that read it and notifies only atoms whose value changed. A single Zustand store notifies every subscriber, and each selector filters.
In Jotai 3, what happens when a component reads an async atom with useAtomValue, and how do you avoid suspending or cancel a stale request?
basics
~20 sAn async atom's value is a promise, so useAtomValue suspends the reader until it settles; a Suspense boundary must sit above it and errors reach an error boundary. unwrap gives a non-suspending view, and the read's signal aborts stale fetches.
A Jotai form component creates its field atoms inside the render body and then loops or loses every typed value; why, and how should per-field atoms be created instead?
basics
~20 sJotai identifies atoms by object identity, so an atom created during render is a brand-new key each time: its value starts over and useAtom can loop. Define atoms at module scope, memoise them with useMemo, or use atomFamily from jotai-family in v3.
In MobX 6, what is the difference between makeObservable with annotations and makeAutoObservable, and when can you not use makeAutoObservable?
basics
~20 smakeObservable(this, { ... }) annotates only the members you list; makeAutoObservable(this) infers them: fields become observable, getters computed, functions autoAction, generators flow. makeAutoObservable cannot be used on classes that have a superclass or are subclassed.
In MobX 6, why does assigning observable state after an await inside a store method trigger a strict-mode warning, and how do you fix it?
basics
~20 sAn action only covers the synchronous part of a function; code after await runs later, outside it. With the default enforceActions: "observed", changing observed state there logs a warning. Wrap the post-await writes in runInAction, or write the method as a flow generator.
A React todo row shows a stale title after a MobX store updates todo.title; what do you check, and why must the component that renders the value be an observer?
basics
~20 sMobX tracks only reads made during an observer's own render. Check that the row is an observer, reads todo.title itself rather than receiving a pre-read string, is not rendered through a non-observer child or callback, and that title is observable.
In MobX, when should derived data be a computed value rather than an autorun or reaction, and when does a computed actually cache?
basics
~20 sUse computed for any value derived from state and reactions only for side effects such as saving or logging. A computed is lazy and caches its result only while something observes it; read outside a reaction, it recomputes on every access.
A MobX autorun in a todo app re-runs more often than expected and keeps running after its screen closes; how do you find what it tracks, and how do you stop it?
basics
~20 sAn autorun depends on everything it read in its last run, so serialising the whole list makes any field change re-run it. trace() or getDependencyTree shows those dependencies, reaction narrows them, and calling the returned disposer on unmount stops it.
In NgRx, why does a flight-search page's search request belong in an effect rather than in the reducer or the component?
basics
~20 sAn NgRx reducer must be a pure, synchronous function from state and action to the next state, so it cannot wait for an API. An effect listens to dispatched actions, runs the request and dispatches a success or failure action back.
In NgRx's @ngrx/entity, why does a kanban board keep its cards as an ids array plus an entities map instead of one array?
basics
~20 sEntityState splits a collection into ids, which hold the order, and entities, a map keyed by id, so a card is found by key instead of a scan, is stored once, and can change without rebuilding the order.
In NgRx 22, why must an on() handler inside createReducer return a new state object instead of mutating the slice it receives?
basics
~20 sAn NgRx reducer is a pure function whose new object is how the store detects a change: selectors and select() compare by reference. In development the default immutability check freezes state, so a mutation throws instead of failing silently.
In NgRx 22, why should an invoicing dashboard's components read the invoice slice through selectors instead of mapping store state themselves?
basics
~20 sSelectors keep knowledge of the state shape in one file, compute derived values such as invoice totals once and cache them, compose into larger reads, and are pure functions that can be tested without a component.
What is an NgRx SignalStore, and how does it differ from an Angular service that keeps its own writable signals?
basics
~20 sAn NgRx SignalStore is an injectable class built by signalStore() from features: withState turns state slices into signals, withComputed and withMethods add derived values and operations, and by default only the store's own methods can change state.
In NGXS, how do you define a state slice with @State, change it from an @Action handler, and register it in a standalone Angular app?
basics
~10 sAn NGXS state is a class decorated with @State({ name, defaults }) and @Injectable(); its @Action(ActionClass) methods receive a StateContext and the action and write through setState or patchState. provideStore([RoomsState]) registers it.
In NGXS 22, how do @Selector methods and createSelector memoize, and how should a standalone component read them now that the @Select decorator is deprecated?
basics
~20 s@Selector marks a static method whose result is cached on its last inputs; createSelector builds the same kind of selector at runtime. Components read them with select(), which returns a signal, or Store.select for an Observable; @Select is deprecated.
In NGXS, how do ctx.setState and ctx.patchState differ, and when should an action handler use state operators such as patch and updateItem instead?
basics
~10 ssetState replaces the whole slice and also accepts a state operator; patchState shallow-merges top-level properties into an object slice. State operators like patch and updateItem express nested immutable updates and keep unchanged references intact.
In an NGXS hotel booking app, a room-search action fires on every keystroke and older results overwrite newer ones; why, and how do cancelUncompleted and ctx.abortSignal fix it?
basics
~20 sNGXS runs async action handlers in parallel, so a slow earlier search can finish last and overwrite newer results. @Action(SearchRooms, { cancelUncompleted: true }) cancels the previous run on each new dispatch; ctx.abortSignal lets async/await code stop too.
As the lead of an Angular hotel booking app that needs a store library, how would you weigh NGXS against NgRx, and what does each choice cost the team?
basics
~20 sNGXS trades NgRx's pure reducers and separate effects for state classes whose DI-enabled @Action handlers update state and do async work in one place. That gives less ceremony and awaitable actions, at the cost of a less explicit, less pure design.
In RxJS, what is the difference between of([1, 2, 3]) and from([1, 2, 3]), and what inputs does from() accept?
basics
~20 sof([1, 2, 3]) emits the array as one value; from([1, 2, 3]) emits 1, 2 and 3 separately. from() converts any ObservableInput - arrays, iterables, promises, async iterables, ReadableStreams or Observables - into an Observable.
In RxJS, how does an Observable differ from a Promise in when work starts, how many values it delivers, and cancellation?
basics
~20 sAn RxJS Observable is lazy: its producer runs only when subscribed, once per subscriber, and can deliver zero, one or many values synchronously or asynchronously. A Promise starts eagerly, settles once, and has no cancel; unsubscribe() stops an Observable's work.
In RxJS, what does distinctUntilChanged() drop from a sensor stream, and why can the same reading still appear twice?
basics
~20 sdistinctUntilChanged() drops a value only when it equals the last value it emitted, using === by default. It remembers one key, so 21, 21, 22, 21 becomes 21, 22, 21; distinct() is the operator that suppresses every earlier repeat.
In RxJS, why should a typeahead search use switchMap rather than mergeMap to call the search API for each query?
basics
~20 sSearch responses can return out of order. mergeMap forwards every response, so a slow reply for an old query can overwrite the newest results. switchMap unsubscribes the previous request when a new query arrives, so only the latest results arrive.
In RxJS, how do debounceTime and throttleTime differ, and which fits autosave while typing versus a scroll-position tracker?
basics
~20 sdebounceTime emits the latest value once the stream has been silent for the given time, which suits autosave after typing pauses. throttleTime emits a value, then ignores the source for a fixed window, which suits steady scroll updates.
In a Pinia order store, how do you write a synchronous addItem action and an asynchronous placeOrder action, and how does a checkout component call them?
basics
~20 sActions are functions: listed under actions in an option store, where they use this and so cannot be arrows, or returned from a setup store. An async action returns a promise the component awaits; failures reach the caller unless handled.
In a Vue 3 app with Pinia 4, what do createPinia(), app.use(pinia) and defineStore('cart', …) each do before a header badge can call useCartStore()?
basics
~20 screatePinia() builds the root container, app.use(pinia) installs it so components can find it, and defineStore('cart', …) only returns a useCartStore function; the store is created on its first call and then shared by every component.
In a Pinia option store for invoices, how do you define a total-with-tax getter, how do components read it, and when does it recompute?
basics
~20 sAdd it under getters, for example subtotal: (state) => …, and read invoice.total as a property. Pinia wraps each getter in one computed per store, so all components share a cached value that recomputes only after state it read changes.
In a Pinia store for a user-preferences panel, when do you write state directly, call $patch with an object, or call $patch with a function?
basics
~20 sDirect writes suit single fields and v-model. $patch with an object applies several fields at once, merging nested objects but replacing arrays. $patch with a function mutates state in place for array edits or logic; both patch forms notify subscribers once.
How do you unit-test a Pinia notifications store's actions and getters, and why call setActivePinia(createPinia()) before each test?
basics
~20 sCall setActivePinia(createPinia()) in beforeEach, then call useNotificationsStore() directly and exercise real actions and getters. A fresh pinia per test gives a fresh store, because stores are cached per pinia and state would otherwise leak between tests.
What is VueUse, and what does calling its useEventListener or useIntersectionObserver in a Vue 3 component save you from writing by hand?
basics
~20 sVueUse is a collection of ready-made Vue 3 composables for browser APIs, sensors and reactive state. useEventListener and useIntersectionObserver attach their listener or observer, follow reactive targets, and remove it automatically when the component's scope is disposed.
In VueUse, how do refDebounced, useDebounceFn and useThrottleFn differ, and which fits a dashboard's search box versus its resize handler?
basics
~20 srefDebounced returns a read-only ref that copies a source ref once it stops changing; useDebounceFn wraps a function so it runs after calls pause; useThrottleFn runs a function at most once per interval. Debounce the search box; throttle the resize handler.
With VueUse's useStorage persisting a dashboard filter to localStorage, why is a newly added filter field undefined for returning users?
basics
~20 suseStorage uses the stored value whenever the key exists and ignores the default, so a filter object saved before the field existed lacks it. mergeDefaults: true shallow-merges the defaults under the stored object; a merge function handles nested fields.
A dashboard panel calls VueUse's useFetch(`/api/stats?range=${props.range}`) and refDebounced on props.search; why does neither react when the props change, and how should you pass them?
basics
~20 sBoth calls receive plain values read once during setup. Pass useFetch a getter URL plus refetch: true, since refetch defaults to false; refDebounced accepts only a Ref, so wrap the prop with toRef(() => props.search).