Global State: Redux vs. Zustand

1. Redux Toolkit (RTK)

Q: Explain the Redux Data Flow.
Action -> Reducer -> Store.
Basic Implementation
// 1. Dispatch Action
dispatch({ type: 'counter/increment' });

// 2. Reducer processes it
function reducer(state, action) {
  if (action.type === 'counter/increment') {
    return { value: state.value + 1 };
  }
  return state;
}

// 3. UI Updates via Selector
const count = useSelector(state => state.value);
Real World Example
// In a Component
<button onClick={() => dispatch(increment())}>
  Count is {count}
</button>
Show Answer
  1. Action: An event describes something that happened. dispatch(addItem(item)).
  2. Reducer: A pure function that takes the current state and action, and returns the new state.
  3. Store: Holds the state. Notifies the UI to subscribe to changes.

RTK Improvement: Uses "Slices" to bundle actions and reducers. Uses "Immer" to allow writing "mutating" logic that is actually immutable.

Q: Code Sample: Create a Slice
createSlice({ name, initialState, reducers })
Basic Implementation
const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => { state.value += 1; }
  }
});
Real World Example
// Async Thunk in Slice
export const fetchUser = createAsyncThunk('users/fetch', async (id) => {
  const response = await api.getUser(id);
  return response.data;
});

const userSlice = createSlice({
  extraReducers: (builder) => {
    builder.addCase(fetchUser.fulfilled, (state, action) => {
      state.data = action.payload;
    });
  }
});

2. Zustand

Q: Why choose Zustand over Redux?
Simplicity, Boilerplate, Bundle size.
Show Answer

Zustand is a "bearbones" state management library.

  • No Boilerplate: No providers, no reducers, no complex dispatch logic. Just a hook.
  • Small Size: Tiny bundle size compared to Redux.
  • Transient Updates: Can update state without causing re-renders (via subscription).
  • Mental Model: It's just a store with functions to update it.
Q: Code Sample: Create a Store
create((set) => ({ ... }))
Implementation
import { create } from 'zustand'

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

// Usage
function BearCounter() {
  const bears = useStore((state) => state.bears)
  return <h1>{bears} around here ...</h1>
}

3. Context API vs. External Libraries

Q: When should you use Context API instead of Redux/Zustand?
Frequency of updates, Prop drilling.
Show Answer

Context API is best for:

  • Low-frequency updates: Theme (Dark/Light), User Language, Auth User data.
  • Static data: Configuration settings.
  • Avoiding Prop Drilling: Passing data down 3-4 levels.

Avoid Context for: High-frequency updates (like a text input or mouse position) because it triggers a re-render of all consumers every time the value changes, leading to performance issues.

3. Comparison Question

Q: When would you use Context API vs. Redux/Zustand?
Frequency of updates.
Show Answer
  • Context API: For low-frequency updates (Theme, User Language, Auth User). If used for high-frequency updates, it causes re-renders in all consumers.
  • Redux/Zustand: For high-frequency updates (Typing in a form, Drag & Drop, Real-time data). They allow components to subscribe to slices of state to avoid unnecessary re-renders.

4. React Query (TanStack Query)

Q: Server State vs. Client State?
Ownership of data.
Show Answer
  • Client State: Data owned by the browser session (UI state, form inputs, modal open/close). Use useState/Zustand.
  • Server State: Data owned by the server (User profile, List of products). It is asynchronous and can be out of date. Use React Query.
Q: Why use React Query instead of useEffect + fetch?
Features out of the box.
Show Answer

React Query handles:

  • Caching & Deduping requests
  • Auto-refetching (on window focus, network reconnect)
  • Loading & Error states
  • Pagination & Infinite Scroll
  • Race condition handling

Doing this manually with useEffect is error-prone and verbose.

Q: What is "stale-while-revalidate"?
Caching strategy.
Show Answer

A strategy where the browser serves stale data from the cache instantly (for speed), while simultaneously fetching fresh data in the background to update the cache and UI.

Q: Explain `staleTime` vs `gcTime` (cacheTime).
Freshness vs. Garbage Collection.
Show Answer
  • staleTime: How long data is considered "fresh". If data is fresh, no fetch happens. Default is 0 (always stale).
  • gcTime: How long inactive data remains in memory before being garbage collected. Default is 5 mins.
Q: How do you invalidate a query?
Refetching data after mutation.
Show Answer
const queryClient = useQueryClient();
// After a successful mutation (POST/PUT)
mutation.mutate(data, {
  onSuccess: () => {
    // Mark 'todos' as stale so they refetch immediately
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  }
});

5. Advanced Redux

Q: What is Redux Middleware?
Interceptors.
Show Answer

Code that sits between dispatching an action and the moment it reaches the reducer. Used for logging, crash reporting, and async tasks (Thunks/Sagas).

Q: Thunks vs. Sagas?
Async handling.
Show Answer
  • Thunks: Functions that return a function. Simple, standard in RTK. Good for basic async logic.
  • Sagas: Uses Generator functions. More powerful, testable, and complex. Good for complex flows (cancellation, debouncing, race conditions).
Q: What is a "Selector"?
Extracting data.
Show Answer

A function that extracts specific pieces of data from the Redux store state. const selectCount = state => state.counter.value. Memoized selectors (Reselect) prevent re-computation if inputs haven't changed.

Q: What is State Normalization?
Database structure in frontend.
Show Answer

Structuring state like a database (ID-based lookup) rather than arrays.
Array: [{ id: 1, name: 'A' }] (Find is O(n))
Normalized: { ids: [1], entities: { 1: { name: 'A' } } } (Find is O(1))
Helps avoid duplication and makes updates easier.

6. Other State Libraries

Q: What is Recoil / Jotai?
Atomic state.
Show Answer

They use an Atomic model. State is split into small "atoms". Components subscribe to specific atoms. This avoids the "top-down" provider issues of Context and the boilerplate of Redux. Great for apps with many interdependent derived states (like a spreadsheet).

Q: What is MobX?
Observables.
Show Answer

Uses Observables and mutable data structures. It automatically tracks dependencies and updates components when data changes. "Anything that can be derived from the application state, should be."

Q: What is XState?
Finite State Machines.
Show Answer

A library for creating Finite State Machines (FSM). It makes state transitions explicit and robust. You define: Idle -> Loading -> Success/Error. It prevents impossible states (e.g., being "Loading" and "Success" at the same time).

7. State Architecture Patterns

Q: What is "Lifting State Up"?
Sharing state between siblings.
Show Answer

Moving state from a child component to its parent so that the parent can pass it down to other children (siblings) that need it.

Q: What is "Derived State"?
Don't store what you can compute.
Show Answer

State that can be calculated from existing state or props. Do not store derived state in useState. Calculate it during render.

Bad: const [fullName, setFullName] = useState(first + last)

Good: const fullName = firstName + lastName

Q: How to persist state across reloads?
LocalStorage.
Show Answer

Sync your state store with localStorage or sessionStorage.
Redux: redux-persist.
Zustand: persist middleware.
React Query: persistQueryClient.

Q: What is "Optimistic UI"?
Predicting success.
Show Answer

Updating the UI immediately as if the server request succeeded, before getting the actual response. If the request fails, you roll back the change. Makes the app feel instant.

Q: Prop Drilling vs. Component Composition?
Passing props vs. Passing children.
Show Answer

Instead of passing user down 5 levels, pass the component that needs user as a child prop.

// Before
<Layout user={user} /> // Layout passes it to Header -> UserMenu

// After
<Layout header={<Header user={user} />} />
Q: When to use `useReducer` instead of `useState`?
Complex logic.
Show Answer

1. When the next state depends on the previous state in complex ways.
2. When you have multiple sub-values (objects) that change together.
3. When you want to separate state logic from the component (easier testing).

Q: What is the "Single Source of Truth" principle?
Don't duplicate data.
Show Answer

Data should exist in only one place. If multiple components need it, lift it up or put it in a store. Duplicating data leads to synchronization bugs.

Q: How to handle Form State?
Controlled vs Libraries.
Show Answer

For simple forms, useState is fine. For complex forms (validation, dirty fields, touched fields), use a library like React Hook Form or Formik. They handle the state management and performance optimization (uncontrolled inputs) for you.

Q: What is "Propagating State"?
Bubbling up.
Show Answer

Passing a callback function from a parent to a child. The child calls the function to update the parent's state. This is the standard way to communicate "up" the tree.

Q: Immutable State Updates?
Why copy objects?
Show Answer

React relies on reference checks (oldState === newState) to decide if it should re-render. If you mutate an object directly, the reference stays the same, and React won't update. Always create a new object/array copy.

Q: Zustand: `set` vs `get`?
Inside actions.
Show Answer

set merges state. get allows you to read the current state inside an action (e.g., to increment a value based on current value).

Q: Redux DevTools?
Time travel.
Show Answer

A browser extension that lets you inspect every action dispatched, view the state diff, and "time travel" (jump back to previous states) to debug logic errors.

Q: Context + useReducer pattern?
Poor man's Redux.
Show Answer

Combining useReducer (for logic) and Context (for passing dispatch down) creates a lightweight global state management system without external libraries. Good for small apps.

8. Advanced Patterns & Performance

Q: What is "State Colocation"?
Keep it close.
Show Answer

The practice of keeping state as close to where it is used as possible. Don't make everything global. If only one component needs it, keep it local. If a subtree needs it, lift it to the common ancestor.

Q: How does `useSyncExternalStore` work?
React 18.
Show Answer

A hook introduced in React 18 for subscribing to external data sources (like Redux, Zustand, or browser APIs). It ensures consistent updates during concurrent rendering features (like Transitions) by preventing "tearing" (visual inconsistencies).

Q: What is "Tearing" in UI?
Inconsistency.
Show Answer

When different parts of the UI show different values for the same state at the same time. This can happen in concurrent rendering if an external store updates while React is in the middle of rendering a tree.

Q: How to handle "Transient State"?
High frequency, low importance.
Show Answer

State that changes very rapidly but doesn't need to trigger a re-render for every change (e.g., tracking mouse position for a tooltip, or scroll position). Use useRef or direct DOM manipulation for performance, then sync to React state only when necessary.

Q: What is the "Proxy Pattern" in state libraries (Valtio/MobX)?
Magic tracking.
Show Answer

Libraries wrap the state object in a JS Proxy. When you access a property (get), the library records that your component depends on it. When you change it (set), the library knows exactly which components to re-render. No manual selectors needed.

Q: How to debug "Zombie Children" in Redux?
Stale props.
Show Answer

A race condition where a child component tries to read data from the store that has already been deleted by a parent's action, causing a crash. Modern react-redux hooks (useSelector) handle this automatically, but it was a common issue with connect.

Q: What is "Structural Sharing"?
Efficiency.
Show Answer

When updating an immutable data structure, you copy the parts that changed, but keep references to the parts that didn't. This saves memory and makes equality checks fast. Libraries like Immer and Immutable.js use this.

Q: How to manage state in Micro-Frontends?
Isolation vs Sharing.
Show Answer

Generally, keep state isolated within each micro-frontend. For shared state (Auth, Theme), use a custom event bus, window object, or a shared library instance (though this couples them). URL state is often the best way to coordinate.

Q: URL as State Manager?
Shareable.
Show Answer

Storing state in the URL (query params, route params) makes it shareable, bookmarkable, and handles browser history (back button) automatically. Great for search filters, pagination, and active tabs.

Q: What is "Prop Drilling" and 3 ways to fix it?
Passing down.
Show Answer

Passing data through many layers of components that don't use it.
Fixes:
1. Composition: Pass components as children.
2. Context API: Provide data at the top.
3. Global Store: Redux/Zustand.

Q: How to test components connected to a Store?
Integration.
Show Answer

Wrap the component in a test-specific Provider with a mocked store/initial state.
render(<Provider store={mockStore}><MyComponent /></Provider>).
Avoid testing implementation details of the store; test the UI outcome.

Q: What is "Batching" in React state updates?
Performance.
Show Answer

React groups multiple state updates into a single re-render for better performance. React 18 introduced "Automatic Batching" which batches updates even inside promises, timeouts, and native event handlers.

Q: How to handle "Race Conditions" in data fetching?
Order matters.
Show Answer

When a user triggers multiple requests quickly (e.g., typing), responses might arrive out of order.
Fix: Use a library like React Query (handles it automatically) or use a cleanup function in useEffect with a boolean flag to ignore stale responses.

Q: What is the "Flux" pattern?
Unidirectional.
Show Answer

The architecture Redux is based on. Action -> Dispatcher -> Store -> View. Data flows in one direction only, making it predictable and easier to debug than two-way binding (MVC).

Q: When to use `useMemo` for state?
Expensive calculations.
Show Answer

Use it to cache the result of an expensive calculation derived from state/props. It runs during render. Do not use it for side effects. It helps prevent performance bottlenecks when the component re-renders.

Q: How to reset state to initial value?
Keys or Actions.
Show Answer

1. Key Prop: Changing the key of a component forces React to destroy and recreate it, resetting all internal state.
2. Store Action: Dispatch a RESET action in Redux/Zustand that sets the state back to initialState.

Q: What is "State Machine" vs "State Management"?
Logic vs Storage.
Show Answer

State Management (Redux) is about storing data.
State Machine (XState) is about modeling the logic and valid transitions between states. You can use them together.

Q: What is "Optimistic Concurrency Control"?
Versioning.
Show Answer

A method to prevent overwriting data. When updating a record, you send the version number you currently have. The server checks if that version matches the database. If not (someone else updated it), the request fails, and you must re-fetch and merge.

Q: How to handle "Deeply Nested State"?
Flatten it.
Show Answer

Deeply nested objects are hard to update immutably (spread hell).
Fixes:
1. Normalize: Flatten the structure.
2. Immer: Use Immer to write mutable-style logic.
3. Lenses/Optics: Functional programming concept (advanced).

Q: What is the difference between `useRef` and `useState` regarding re-renders?
Triggering updates.
Show Answer

useState: Updating the state triggers a re-render of the component.
useRef: Updating the .current property does not trigger a re-render. Useful for mutable values that don't affect the visual output (timers, DOM elements).