Trademark
Ahmedabad, India
BlogFebruary 15, 2025

Zustand vs Redux: When I Use Each

A comparison from apps I shipped, and when Redux Toolkit is still the better fit
Kuldeep Modi
Zustand vs Redux: When I Use Each
If you are choosing a client-state library for a React app, start here. I have shipped both. Zustand is my default for a new app or a small feature. I still reach for Redux Toolkit when the team, the debugging tools, or an existing store already depend on it. The sections below are the comparison, then the projects that taught me the difference. I use Zustand when:
  • The app is new, or the feature is small to medium
  • The state is a handful of flags, preferences, or UI
  • I want one file and no Provider around the tree
I still use Redux Toolkit when:
  • The team is large and already standardized on Redux: consistency matters more than my preference.
  • We need advanced DevTools: time-travel and action replay can be worth the cost.
  • We have complex middleware needs: sagas, strict logging, or very specific side-effect pipelines.
  • The product is an existing Redux codebase: rewriting for Zustand is not always justified.
So: Zustand for new projects and smaller apps; Redux when the context already demands it.
Aspect
Redux (with RTK)
Zustand
BoilerplateSlices, actions, store configSingle store function
ProviderRequiredNot needed
DevToolsExcellent (time-travel)Basic (state snapshots)
Learning curveSteeperGentle
Published package size13.3 kB gzipped (Toolkit 2.12.0)489 bytes gzipped (5.0.15)
MiddlewareRich ecosystemMinimal (you can add)
Best forLarge teams, strict patterns, complex flowsSmall–medium apps, fast iteration
Those sizes are the published packages on Bundlephobia for Zustand 5.0.15 and Redux Toolkit 2.12.0, not a measurement of one of my production bundles. react-redux is a separate peer of Redux Toolkit and is not included in that 13.3 kB. I used Redux for years. Actions, reducers, dispatch, selectors, and eventually Redux Toolkit. I have shipped production apps with it and it worked. Over time I started asking whether I needed all of that for every project. For most of what I build now, I do not. That is why Zustand became my default for client-side state. This is not a “Redux is bad” story. It is about fit. Redux gave me:
  • Predictable state updates with a single store and clear data flow
  • DevTools for time-travel debugging and inspecting every action
  • Middleware for side effects (API calls, logging)
  • Ecosystem and patterns that many developers already know
For large apps with lots of shared state and strict requirements (the multilanguage admin panel I wrote about earlier), Redux was a solid choice. The structure forced consistency and made onboarding easier once everyone learned the pattern. Boilerplate. Even with Redux Toolkit, you still write slices, actions, and often selectors. For a simple “user preferences” or “UI panel open/closed” state, that is a lot of files and ceremony. Then you wire it into the store, and in components you use useSelector and useDispatch. For a handful of flags, it still feels heavy. Mental overhead. New developers (or me, after a break from a codebase) have to think: Where is the action? Where is the reducer? Which slice does this live in? That is fine when the problem is complex. For “toggle sidebar” it is more than I want to maintain. Provider and setup. You need a Provider around the app and a configured store. Not a dealbreaker, but it is one more layer. In Next.js, or when you are composing many providers (theme, i18n, and so on), the tree gets noisy. Not everything needs a global store. A lot of state is local (form fields, dropdown open state) or can be server state (React Query, SWR). I was putting things in Redux “just in case” we would need them globally later. That led to a bloated store and unnecessary re-renders when we subscribed to big slices. Zustand is a small state library from the same ecosystem as React Three Fiber (Poimandres). It gives you a store with a minimal API and no provider. What I like about it:
  • Minimal boilerplate: One file, one function, and you have a store.
  • No provider: You create a store and use it. No wrapping the app.
  • Simple API: getState(), setState(), and a useStore hook. Easy to read and teach.
  • TypeScript-friendly: Typing the store is straightforward.
  • Small published size: 489 bytes gzipped for version 5.0.15, from the Bundlephobia link above.
  • Flexible: Use it for global state, or scope it to a part of the tree.
The same UI state in Zustand: In a component: No actions, no dispatch, no slice. Just a hook and plain functions. For me, that is the right level of abstraction for most client state. If you are trying Zustand inside a Redux app:
  1. Start at the edges: Pick one slice (UI or feature flags) and replace it with a Zustand store. Keep Redux for the rest.
  2. Match the shape: Design the Zustand store so components can switch with minimal changes (same field names, similar “selectors” via the hook).
  3. Split by domain: Use multiple small stores (useUIStore, useAuthStore) instead of one giant store. It keeps things readable and avoids unnecessary re-renders.
  4. Use selectors: Zustand’s useStore(selector) only re-renders when the selected slice changes, similar to useSelector. Use it for performance.
For most of my projects, Redux’s benefits no longer outweigh the cost. Zustand gives me enough structure without the boilerplate, and I can move faster without sacrificing clarity. For a new React app, it is my default for client state. For a bigger or Redux-heavy codebase, I still choose Redux Toolkit when the context demands it. If you are on the fence, try Zustand in a small feature or a side project. You might find, as I did, that a lot of your state does not need Redux’s power.

Thinking about state management for your app?

I can help you choose and implement the right approach.
Share this post:

Recent posts