Skip to main content

πŸ“˜ React useReducer β€” Complete In-Depth Guide


1. Introduction

πŸ”Ή What is useReducer

useReducer is a React Hook used for managing complex state logic in a component. It is an alternative to useState, especially useful when:
  • State has multiple sub-values
  • State transitions depend on previous state
  • Logic is complex or reusable
It follows the Reducer pattern, inspired by functional programming and state management libraries like Redux.

πŸ”Ή Why it is important in React

  • Centralizes state logic in one place
  • Makes state transitions predictable
  • Improves readability for complex components
  • Encourages pure functions and better testability
  • Scales better than useState for non-trivial logic

πŸ”Ή When and why we use it

Use useReducer when:

βœ… Good use cases:

  • Complex state transitions
  • Multiple related state variables
  • Deeply nested updates
  • Business logic-heavy components
  • Form management (with multiple fields)
  • State depends heavily on previous state

❌ Avoid when:

  • State is simple (use useState)
  • No complex transitions or logic

2. Concepts / Internal Workings


πŸ”Ή Core Concepts

1. Reducer Function

A pure function that determines how state changes.
(state, action) => newState
  • Receives current state
  • Receives an action
  • Returns new state (immutable update)

2. Action

An object describing what happened
{ type: "INCREMENT" }
{ type: "ADD_TODO", payload: "Learn React" }

3. Dispatch

A function used to trigger state changes
dispatch({ type: "INCREMENT" });

4. State

The current value managed by the reducer

πŸ”Ή How it works internally in React

  1. Component calls useReducer(reducer, initialState)
  2. React:
    • Stores state internally
    • Returns [state, dispatch]
  3. When dispatch(action) is called:
    • React calls reducer with (currentState, action)
    • Gets new state
    • Triggers re-render

βš™οΈ Key Internal Behavior

  • React uses reference comparison (Object.is) to detect changes
  • If reducer returns same object β†’ no re-render
  • If new object β†’ re-render occurs

πŸ”Ή Relationship with other React features

πŸ”Έ useState

  • useState is a simplified version of useReducer
  • Internally, React implements useState using a reducer-like pattern

πŸ”Έ useContext

  • Common pairing:
    • useReducer β†’ manages state
    • useContext β†’ shares state globally

πŸ”Έ React Rendering

  • Dispatch triggers a reconciliation cycle
  • React schedules updates efficiently (especially in concurrent mode)

πŸ”Έ Redux

  • useReducer is a local Redux-like pattern
  • No middleware, but same core idea

3. Syntax & Examples


πŸ”Ή Basic Syntax

const [state, dispatch] = useReducer(reducer, initialState);

πŸ”Ή Example 1: Counter

import React, { useReducer } from "react";

const reducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      return state;
  }
};

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement" })}>
        -
      </button>
    </div>
  );
}

πŸ”Ή Example 2: Complex State Object

const reducer = (state, action) => {
  switch (action.type) {
    case "updateName":
      return { ...state, name: action.payload };
    case "updateAge":
      return { ...state, age: action.payload };
    default:
      return state;
  }
};

πŸ”Ή Example 3: Todo List

const reducer = (state, action) => {
  switch (action.type) {
    case "add":
      return [...state, { id: Date.now(), text: action.payload }];
    case "remove":
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
};

πŸ”Ή Example 4: Lazy Initialization

const init = (initialValue) => {
  return { count: initialValue };
};

const [state, dispatch] = useReducer(reducer, 10, init);
πŸ‘‰ Useful when initial state is expensive to compute

πŸ”Ή Example 5: useReducer + useContext

const AppContext = React.createContext();

const AppProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <AppContext.Provider value={{ state, dispatch }}>
      {children}
    </AppContext.Provider>
  );
};

πŸ”Ή Mini Variations

Dispatch with payload

dispatch({ type: "SET_VALUE", payload: 42 });

Multiple reducers (manual composition)

const rootReducer = (state, action) => ({
  user: userReducer(state.user, action),
  cart: cartReducer(state.cart, action),
});

4. Edge Cases / Common Mistakes


πŸ”΄ 1. Mutating state directly

❌ Wrong:
state.count += 1;
return state;
βœ… Correct:
return { ...state, count: state.count + 1 };
πŸ‘‰ Mutation prevents React from detecting changes

πŸ”΄ 2. Returning same reference

return state;
πŸ‘‰ No re-render occurs

πŸ”΄ 3. Missing default case

default:
  return state;
πŸ‘‰ Without this, unexpected bugs may occur

πŸ”΄ 4. Dispatch inside render

dispatch({ type: "increment" }); // ❌ BAD
πŸ‘‰ Causes infinite re-renders

πŸ”΄ 5. Overusing useReducer

πŸ‘‰ Not every state needs reducer logic πŸ‘‰ Adds unnecessary complexity

πŸ”΄ 6. Deep nested updates

return {
  ...state,
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      name: action.payload
    }
  }
};
πŸ‘‰ Hard to maintain β†’ consider normalization

πŸ”΄ 7. Async logic inside reducer

❌ Avoid:
case "fetch":
  fetch(...);
πŸ‘‰ Reducers must be pure functions

πŸ”΄ 8. Dispatch identity misunderstanding

  • dispatch is stable across renders
  • Safe to pass down without memoization

5. Best Practices


βœ… 1. Keep reducer pure

  • No side effects
  • No API calls
  • No randomness

βœ… 2. Use action constants

const ACTIONS = {
  INCREMENT: "increment",
  DECREMENT: "decrement"
};
πŸ‘‰ Prevents typos and improves maintainability
  • One reducer per domain (user, cart, form)
  • Avoid giant reducers

βœ… 4. Use meaningful action names

❌ "SET" βœ… "SET_USER_NAME"

βœ… 5. Normalize complex state

Instead of deep nesting:
{
  users: {
    byId: {},
    allIds: []
  }
}

βœ… 6. Combine with useContext for global state

  • Lightweight alternative to Redux
  • Good for medium-sized apps

βœ… 7. Optimize re-renders

  • Avoid unnecessary object creation
  • Split reducers if needed

βœ… 8. Lazy initialization for heavy state

useReducer(reducer, initialArg, init);

βœ… 9. Debugging strategy

  • Log actions inside reducer:
console.log(action);
  • Track state transitions

βœ… 10. Testing reducers

  • Reducers are pure β†’ easy to test
expect(reducer({ count: 0 }, { type: "increment" }))
  .toEqual({ count: 1 });

🧠 Final Mental Model

Think of useReducer as:
β€œA state machine where actions describe events, and reducer defines how state evolves.”

🧠 Advanced useReducer β€” Senior-Level Conceptual Questions & Answers


1. How is useReducer fundamentally different from useState under the hood?

βœ… Answer

At a high level, both are similar β€” React internally models useState as a specialized reducer.

πŸ” Key Differences

AspectuseStateuseReducer
APISimple setterDispatch + reducer
Logic locationInlineCentralized
State transitionsImplicitExplicit

βš™οΈ Internal Insight

React internally does something like:
// Conceptually
function useState(initial) {
  return useReducer((state, action) => action, initial);
}

🧠 WHY it matters

  • useReducer gives predictable state transitions
  • Better for debugging (action logs)
  • More scalable for complex logic

2. Why must a reducer be a pure function, and what breaks if it’s not?

βœ… Answer

Reducers must be pure because React relies on:
  • Deterministic state updates
  • Safe re-execution (especially in concurrent rendering)

❌ Problem with impure reducers

function reducer(state, action) {
  fetch("/api"); // ❌ side effect
  return state;
}

πŸ”₯ What breaks?

  • React may call reducer multiple times
  • Causes duplicate API calls
  • Leads to inconsistent UI

🧠 WHY

React may replay updates during rendering (Concurrent Mode)

3. How does React determine whether to re-render after dispatch?

βœ… Answer

React uses reference equality (Object.is).
if (Object.is(prevState, newState)) {
  // No re-render
}

πŸ”΄ Subtle Bug

state.count++;
return state; // ❌ same reference
πŸ‘‰ No re-render happens

🧠 WHY

React assumes:
  • Same reference = no change
  • New reference = update required

4. What are the trade-offs between useReducer and Redux?

βœ… Answer

πŸ” Comparison

FeatureuseReducerRedux
ScopeLocalGlobal
MiddlewareβŒβœ…
DevToolsβŒβœ…
BoilerplateLowHigh

🧠 When to choose

  • useReducer β†’ component-level or medium complexity
  • Redux β†’ large apps, cross-cutting concerns

🧠 WHY

Redux adds:
  • Middleware pipeline
  • Time-travel debugging
  • Predictable global architecture

5. Why is dispatch stable across renders, and why does that matter?

βœ… Answer

React guarantees that dispatch is referentially stable.
const [state, dispatch] = useReducer(...);
// dispatch !== recreated on re-render

🧠 WHY

  • Avoid unnecessary re-renders in children
  • Safe to pass down without useCallback

πŸ”₯ Contrast

const fn = () => {}; // recreated every render

6. What happens if multiple dispatches occur synchronously?

βœ… Answer

React batches updates.
dispatch({ type: "increment" });
dispatch({ type: "increment" });

Result:

  • Reducer runs twice
  • Final state reflects both updates

🧠 WHY

React queues updates and processes them in order

7. How does lazy initialization in useReducer improve performance?

βœ… Answer

const [state, dispatch] = useReducer(reducer, initialArg, init);

🧠 WHY

  • init runs only once
  • Avoids recomputation on every render

πŸ”₯ Use case

const init = () => expensiveCalculation();

8. How would you structure reducers in a large-scale application?

βœ… Answer

πŸ”Ή Pattern: Reducer Composition

const rootReducer = (state, action) => ({
  user: userReducer(state.user, action),
  cart: cartReducer(state.cart, action),
});

🧠 WHY

  • Separation of concerns
  • Maintainability
  • Scalable architecture

9. Why is putting async logic inside reducers an anti-pattern?

βœ… Answer

Reducers must be pure and synchronous.

❌ Bad

case "FETCH":
  fetch("/api"); // ❌

βœ… Correct

useEffect(() => {
  fetchData().then(data =>
    dispatch({ type: "SUCCESS", payload: data })
  );
}, []);

🧠 WHY

  • Async logic breaks predictability
  • Hard to test
  • Violates functional programming principles

10. How does useReducer behave in concurrent rendering?

βœ… Answer

React may:
  • Pause
  • Resume
  • Replay reducer calls

🧠 WHY

Concurrent rendering allows:
  • Interruptible updates
  • Better UX

⚠️ Implication

Reducers must be:
  • Pure
  • Idempotent

11. What are the pitfalls of deeply nested state in reducers?

βœ… Answer

return {
  ...state,
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      name: action.payload
    }
  }
};

πŸ”΄ Problems

  • Verbose
  • Error-prone
  • Hard to maintain

βœ… Solution

  • Normalize state
  • Split reducers

12. When does useReducer become an anti-pattern?

βœ… Answer

❌ Overuse scenarios

  • Simple boolean toggles
  • Independent state variables
const [count, setCount] = useState(0); // better

🧠 WHY

  • Adds unnecessary abstraction
  • Reduces readability

13. How do you debug complex reducer logic?

βœ… Answer

πŸ”Ή Techniques

  1. Log actions
console.log(action);
  1. Trace state transitions
  2. Use custom middleware-like wrappers
const dispatchWithLog = action => {
  console.log(action);
  dispatch(action);
};

🧠 WHY

Reducers are deterministic β†’ easy to trace

14. How does useReducer interact with useContext in global state design?

βœ… Answer

πŸ”Ή Pattern

const Context = createContext();

const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <Context.Provider value={{ state, dispatch }}>
      {children}
    </Context.Provider>
  );
};

🧠 WHY

  • Avoid prop drilling
  • Share state globally

⚠️ Trade-off

  • All consumers re-render unless optimized

15. How can you prevent unnecessary re-renders when using useReducer + Context?

βœ… Answer

πŸ”Ή Techniques

  • Split contexts
  • Memoize selectors
  • Use libraries like Zustand/Recoil if needed

🧠 WHY

Context updates trigger all consumers

16. Why are action objects preferred over direct function calls?

βœ… Answer

dispatch({ type: "ADD_TODO", payload: "Task" });

🧠 WHY

  • Declarative
  • Serializable
  • Easier debugging
  • Enables logging/history

17. What happens if the reducer throws an error?

βœ… Answer

  • Component crashes
  • Error propagates to nearest error boundary

🧠 WHY

Reducers run during render phase

βœ… Solution

  • Validate inputs
  • Use error boundaries

18. How would you model a finite state machine using useReducer?

βœ… Answer

const reducer = (state, action) => {
  switch (state.status) {
    case "idle":
      if (action.type === "FETCH") return { status: "loading" };
      break;
    case "loading":
      if (action.type === "SUCCESS") return { status: "success" };
      break;
  }
  return state;
};

🧠 WHY

  • Explicit transitions
  • Prevent invalid states

πŸ”š Final Thought

At a senior level, useReducer is not just a hook β€” it’s:
A predictable state transition system that helps you model complex UI logic with clarity and control.

🧠 Advanced useReducer β€” Senior-Level MCQs


1. When will a component NOT re-render after a dispatch?

Question:
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      state.count += 1;
      return state;
    default:
      return state;
  }
}
What happens after dispatch({ type: "increment" })?

Options:

A. Component re-renders with updated count B. Component does not re-render C. React throws an error D. Behavior is undefined

βœ… Correct Answer: B

πŸ’‘ Explanation:

React compares state using reference equality (Object.is). Since the same object reference is returned, React skips re-render.

❌ Why others are wrong:

  • A: Incorrect β€” mutation doesn’t trigger re-render
  • C: No runtime error
  • D: Behavior is deterministic

2. What happens if reducer returns a completely new object but with identical values?

Options:

A. No re-render B. Re-render occurs C. React shallow compares properties D. React throws warning

βœ… Correct Answer: B

πŸ’‘ Explanation:

React only checks reference, not deep equality. New object β†’ re-render.

❌ Others:

  • A: Wrong β€” reference changed
  • C: React does NOT deep/shallow compare
  • D: No warning

3. What is the biggest risk of putting side effects inside a reducer?

Options:

A. Performance degradation B. Duplicate or inconsistent side effects C. Memory leaks D. Reducer stops working

βœ… Correct Answer: B

πŸ’‘ Explanation:

React may re-run reducers (especially in concurrent mode), causing duplicate API calls or inconsistent behavior.

❌ Others:

  • A: Secondary issue
  • C: Not primary concern
  • D: Reducer still works

4. What guarantees that dispatch does not change between renders?

Options:

A. React memoizes it with useCallback B. It is bound once during hook initialization C. It is recreated every render but optimized D. JavaScript closures

βœ… Correct Answer: B

πŸ’‘ Explanation:

React internally ensures dispatch is stable, created once per hook.

❌ Others:

  • A: Not user-level memoization
  • C: Not recreated
  • D: Partial but not the full reason

5. What happens with multiple synchronous dispatches?

dispatch({ type: "inc" });
dispatch({ type: "inc" });

Options:

A. Only last dispatch applies B. Both dispatches apply sequentially C. Only first applies D. Behavior depends on batching

βœ… Correct Answer: B

πŸ’‘ Explanation:

React queues updates β†’ reducer runs twice β†’ both applied.

❌ Others:

  • A/C: Incorrect
  • D: Batching doesn’t skip updates

6. What is the purpose of the third argument in useReducer?

Options:

A. Middleware B. Lazy initialization C. Debugging D. Async dispatch handling

βœ… Correct Answer: B

πŸ’‘ Explanation:

useReducer(reducer, initialArg, initFn);
initFn computes initial state once.

❌ Others:

  • A: Not supported
  • C/D: Not related

7. What happens if reducer throws an error?

Options:

A. Ignored silently B. Component crashes C. State resets D. React retries reducer

βœ… Correct Answer: B

πŸ’‘ Explanation:

Reducers run during render β†’ error propagates β†’ nearest error boundary.

❌ Others:

  • A: No silent fail
  • C: No reset
  • D: No retry

8. Which scenario justifies using useReducer over useState?

Options:

A. Managing a single boolean B. Handling multiple independent states C. Complex state transitions dependent on previous state D. Static values

βœ… Correct Answer: C

πŸ’‘ Explanation:

Reducer shines when transitions are complex and interdependent.

❌ Others:

  • A/B/D: Better suited for useState

9. What is a key downside of combining useReducer with useContext?

Options:

A. Dispatch becomes unstable B. All consumers re-render on state change C. Reducer cannot handle nested state D. Actions become async

βœ… Correct Answer: B

πŸ’‘ Explanation:

Context triggers re-render for all consumers.

❌ Others:

  • A: Dispatch is stable
  • C: Reducer can handle nested state
  • D: Actions remain sync

10. What is the effect of missing a default case in reducer?

Options:

A. React throws error B. State becomes undefined C. No effect D. Reducer stops executing

βœ… Correct Answer: B

πŸ’‘ Explanation:

If no case matches and no default β†’ returns undefined β†’ breaks state.

❌ Others:

  • A/D: Not automatic
  • C: Incorrect

11. Why are action objects preferred over direct function calls?

Options:

A. Faster execution B. Easier serialization and debugging C. Required by React D. Enables async behavior

βœ… Correct Answer: B

πŸ’‘ Explanation:

Action objects:
  • Loggable
  • Serializable
  • Traceable

❌ Others:

  • A: No speed benefit
  • C: Not required
  • D: Not inherent

12. What is the biggest issue with deeply nested state updates?

Options:

A. Performance B. Verbosity and maintainability C. React incompatibility D. Dispatch failure

βœ… Correct Answer: B

πŸ’‘ Explanation:

Deep updates become:
  • Hard to read
  • Error-prone

❌ Others:

  • A: Minor
  • C/D: Not true

13. Why is this pattern problematic?

dispatch({ type: "SET", payload: Math.random() });

Options:

A. Random values not allowed B. Breaks purity expectations C. Causes memory leaks D. No issue

βœ… Correct Answer: B

πŸ’‘ Explanation:

Reducers should be deterministic; randomness breaks predictability.

❌ Others:

  • A: Not restricted
  • C: No leak
  • D: Incorrect

14. What happens if reducer returns undefined?

Options:

A. React ignores update B. Component crashes C. State becomes undefined D. React retries reducer

βœ… Correct Answer: C

πŸ’‘ Explanation:

State becomes undefined, likely causing runtime issues.

❌ Others:

  • A: Not ignored
  • B: Not immediate crash
  • D: No retry

15. How does useReducer help in modeling state machines?

Options:

A. By storing multiple states B. By enforcing valid transitions C. By preventing re-renders D. By batching updates

βœ… Correct Answer: B

πŸ’‘ Explanation:

Reducer defines allowed transitions β†’ avoids invalid states.

❌ Others:

  • A: Not unique
  • C/D: Unrelated

16. What is the consequence of dispatching inside render?

dispatch({ type: "increment" });

Options:

A. One extra render B. Infinite re-render loop C. No effect D. React warning only

βœ… Correct Answer: B

πŸ’‘ Explanation:

Dispatch β†’ state update β†’ render β†’ dispatch again β†’ infinite loop.

❌ Others:

  • A: Not limited
  • C: Incorrect
  • D: It crashes, not just warns

17. Why is splitting reducers beneficial?

Options:

A. Improves React performance automatically B. Improves maintainability and separation C. Reduces memory usage D. Enables async reducers

βœ… Correct Answer: B

πŸ’‘ Explanation:

Separation of concerns β†’ easier scaling and debugging.

❌ Others:

  • A: Not automatic
  • C: Minimal impact
  • D: Not related

18. What is a subtle performance issue with large reducers?

Options:

A. React skips updates B. Reducer runs on every dispatch C. Dispatch becomes async D. State becomes stale

βœ… Correct Answer: B

πŸ’‘ Explanation:

Every dispatch executes reducer β†’ large logic can slow updates.

❌ Others:

  • A: Not true
  • C: Dispatch is sync
  • D: Not inherent

πŸ”š Final Insight

At a senior level, these questions test whether you understand:
  • State identity vs value
  • Purity and concurrency implications
  • Architectural trade-offs
  • Real-world scaling issues

🧠 Advanced useReducer Coding Problems (Senior-Level)


1. Shopping Cart with Derived Totals

🧩 Problem

Build a shopping cart where:
  • Users can add/remove/update items
  • Each item has price, quantity
  • Show total items and total price

βš™οΈ Constraints

  • Avoid recalculating totals outside reducer
  • Handle duplicate items (merge quantities)

βœ… Expected Behavior

addItem({ id: 1, price: 100 })
addItem({ id: 1, price: 100 })
β†’ quantity = 2, total = 200

⚠️ Edge Cases

  • Removing non-existent item
  • Quantity = 0 β†’ remove item

🧠 Solution Approach

Step 1: State Shape

{
  items: [],
  totalPrice: 0,
  totalItems: 0
}

Step 2: Reducer Logic

function reducer(state, action) {
  switch (action.type) {
    case "ADD":
      // merge or insert
    case "REMOVE":
    case "UPDATE_QTY":
  }
}

Step 3: Why reducer?

  • Derived state consistency
  • Avoid scattered logic

2. Multi-Step Form Wizard

🧩 Problem

Create a form with multiple steps:
  • Step navigation (next/back)
  • Store form data across steps
  • Reset functionality

βš™οΈ Constraints

  • Cannot lose previous step data
  • Validation per step

βœ… Expected Behavior

  • Step 1 β†’ Step 2 β†’ Back β†’ data persists

⚠️ Edge Cases

  • Jumping steps
  • Invalid inputs

🧠 Solution

State

{
  step: 1,
  data: {
    name: "",
    email: ""
  }
}

Reducer

case "NEXT":
case "BACK":
case "UPDATE_FIELD":

Insight

Reducer ensures state + navigation consistency

3. Undo/Redo System

🧩 Problem

Implement undo/redo functionality for text editing.

βš™οΈ Constraints

  • Maintain history
  • Limit history size (e.g., 10)

βœ… Expected

type "A" β†’ "AB" β†’ undo β†’ "A"

⚠️ Edge Cases

  • Undo beyond history
  • Redo after new action β†’ clear redo stack

🧠 Solution

State

{
  past: [],
  present: "",
  future: []
}

Reducer

case "TYPE":
case "UNDO":
case "REDO":

WHY

Reducer models state timeline

4. API Request State Manager

🧩 Problem

Handle API states:
  • loading
  • success
  • error

βš™οΈ Constraints

  • Avoid inconsistent states

βœ… Expected

FETCH β†’ loading
SUCCESS β†’ data
ERROR β†’ error

⚠️ Edge Cases

  • Multiple requests
  • Stale responses

🧠 Solution

{
  status: "idle" | "loading" | "success" | "error",
  data: null,
  error: null
}

5. Dynamic Form Builder

🧩 Problem

Form fields are dynamic (add/remove fields at runtime)

βš™οΈ Constraints

  • Fields stored as array
  • Each field has validation

⚠️ Edge Cases

  • Duplicate field IDs
  • Removing active field

🧠 Solution

Reducer manages:
fields: [{ id, value, error }]

6. Notification Queue System

🧩 Problem

Manage toast notifications:
  • Add/remove notifications
  • Auto-dismiss

βš™οΈ Constraints

  • FIFO order

⚠️ Edge Cases

  • Duplicate messages
  • Rapid dispatch

🧠 Solution

case "ADD_NOTIFICATION"
case "REMOVE_NOTIFICATION"

7. File Upload Manager

🧩 Problem

Track multiple file uploads with progress

βš™οΈ Constraints

  • Each file has progress %
  • Handle cancel

⚠️ Edge Cases

  • Cancel mid-upload
  • Retry

🧠 Solution

files: [{ id, progress, status }]

8. Role-Based Access Control

🧩 Problem

Manage user roles and permissions dynamically

βš™οΈ Constraints

  • Roles can change at runtime

⚠️ Edge Cases

  • Conflicting permissions

🧠 Solution

Reducer enforces:
case "SET_ROLE"
case "UPDATE_PERMISSION"

9. Drag-and-Drop List Reordering

🧩 Problem

Reorder items in a list

βš™οΈ Constraints

  • Maintain immutability

⚠️ Edge Cases

  • Same index move
  • Invalid indices

🧠 Solution

case "MOVE_ITEM"

10. Theme Manager (Global State)

🧩 Problem

Toggle and persist theme

βš™οΈ Constraints

  • Sync with localStorage

⚠️ Edge Cases

  • Initial load mismatch

🧠 Solution

Reducer + useEffect

11. Pagination State Manager

🧩 Problem

Handle page navigation + page size

βš™οΈ Constraints

  • Reset page on size change

⚠️ Edge Cases

  • Out-of-range pages

🧠 Solution

{ page, pageSize }

12. Form Validation Engine

🧩 Problem

Validate multiple fields with rules

βš™οΈ Constraints

  • Real-time validation

⚠️ Edge Cases

  • Async validation

🧠 Solution

Reducer stores:
values, errors

13. Shopping Wishlist with Sync

🧩 Problem

Sync wishlist with backend

βš™οΈ Constraints

  • Optimistic updates

⚠️ Edge Cases

  • API failure rollback

🧠 Solution

case "ADD_OPTIMISTIC"
case "ROLLBACK"

14. Tab Manager

🧩 Problem

Manage dynamic tabs

βš™οΈ Constraints

  • Only one active tab

⚠️ Edge Cases

  • Closing active tab

🧠 Solution

tabs, activeTab

15. Keyboard Shortcut Manager

🧩 Problem

Map keyboard shortcuts to actions

βš™οΈ Constraints

  • Prevent conflicts

⚠️ Edge Cases

  • Duplicate bindings

🧠 Solution

Reducer manages:
shortcuts: {}

16. Collaborative Cursor Tracker

🧩 Problem

Track multiple user cursors (like Figma)

βš™οΈ Constraints

  • Real-time updates

⚠️ Edge Cases

  • Disconnect cleanup

🧠 Solution

cursors: { userId: position }

17. Filter + Sort Engine

🧩 Problem

Apply filters and sorting to dataset

βš™οΈ Constraints

  • Combine multiple filters

⚠️ Edge Cases

  • Empty results

🧠 Solution

Reducer manages:
filters, sort, data

18. Chat Message Buffer

🧩 Problem

Handle incoming messages + scroll behavior

βš™οΈ Constraints

  • Limit buffer size

⚠️ Edge Cases

  • Duplicate messages

🧠 Solution

messages: []

19. Game State Manager (Finite State Machine)

🧩 Problem

Game states:
  • idle β†’ playing β†’ paused β†’ game over

βš™οΈ Constraints

  • Invalid transitions not allowed

⚠️ Edge Cases

  • Pause from idle

🧠 Solution

Reducer enforces transitions

πŸ”š Final Thought

These problems test:
  • State modeling
  • Reducer design
  • Edge-case thinking
  • Real-world architecture

🧠 Advanced useReducer Debugging Challenges (Senior-Level)


1. Silent State Mutation Bug

🐞 Buggy Code

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      state.count += 1;
      return state;
    default:
      return state;
  }
}

❌ What’s Wrong

State is being mutated directly.

πŸ€” WHY it happens

React relies on reference equality (Object.is). Returning the same object prevents re-render.

βœ… Fix

return { ...state, count: state.count + 1 };

🧠 Best Practice

Always treat state as immutable.

2. Missing Default Case Crash

🐞 Buggy Code

function reducer(state, action) {
  if (action.type === "increment") {
    return { count: state.count + 1 };
  }
}

❌ What’s Wrong

No return for unknown actions β†’ returns undefined.

πŸ€” WHY

React expects reducer to always return valid state.

βœ… Fix

default:
  return state;

🧠 Best Practice

Always include a default fallback.

3. Infinite Re-render Loop

🐞 Buggy Code

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  dispatch({ type: "increment" });

  return <div>{state.count}</div>;
}

❌ What’s Wrong

Dispatch inside render.

πŸ€” WHY

Dispatch β†’ state update β†’ render β†’ dispatch again β†’ infinite loop.

βœ… Fix

useEffect(() => {
  dispatch({ type: "increment" });
}, []);

🧠 Best Practice

Never trigger state updates during render.

4. Async Logic Inside Reducer

🐞 Buggy Code

function reducer(state, action) {
  switch (action.type) {
    case "fetch":
      fetch("/api").then(() => {});
      return state;
  }
}

❌ What’s Wrong

Side effects inside reducer.

πŸ€” WHY

Reducers must be pure; React may re-run them.

βœ… Fix

useEffect(() => {
  fetch("/api").then(data =>
    dispatch({ type: "success", payload: data })
  );
}, []);

🧠 Best Practice

Keep reducers pure and synchronous.

5. Stale State Assumption

🐞 Buggy Code

dispatch({ type: "increment" });
console.log(state.count);

❌ What’s Wrong

Expecting updated state immediately.

πŸ€” WHY

State updates are scheduled, not immediate.

βœ… Fix

Use useEffect:
useEffect(() => {
  console.log(state.count);
}, [state.count]);

🧠 Best Practice

Treat state updates as async from UI perspective.

6. Recreating Initial State Expensively

🐞 Buggy Code

const [state, dispatch] = useReducer(reducer, expensiveInit());

❌ What’s Wrong

expensiveInit() runs on every render.

πŸ€” WHY

Initializer is executed immediately.

βœ… Fix

useReducer(reducer, initialArg, expensiveInit);

🧠 Best Practice

Use lazy initialization.

7. Deeply Nested State Update Bug

🐞 Buggy Code

return {
  ...state,
  user: {
    profile: {
      name: action.payload
    }
  }
};

❌ What’s Wrong

Overwrites entire user.profile structure.

πŸ€” WHY

Missing spread for nested objects.

βœ… Fix

return {
  ...state,
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      name: action.payload
    }
  }
};

🧠 Best Practice

Always preserve nested structure.

8. Dispatching Wrong Action Shape

🐞 Buggy Code

dispatch("increment");

❌ What’s Wrong

Reducer expects object, not string.

πŸ€” WHY

Mismatch in action contract.

βœ… Fix

dispatch({ type: "increment" });

🧠 Best Practice

Standardize action shape.

9. Unnecessary Re-renders with Context

🐞 Buggy Code

<Context.Provider value={{ state, dispatch }}>

❌ What’s Wrong

New object every render β†’ re-renders all consumers.

πŸ€” WHY

Reference changes trigger context updates.

βœ… Fix

const value = useMemo(() => ({ state, dispatch }), [state]);

🧠 Best Practice

Memoize provider values.

10. Reducer Too Large (Performance Issue)

🐞 Buggy Code

function reducer(state, action) {
  // 500+ lines of logic
}

❌ What’s Wrong

Heavy computation on every dispatch.

πŸ€” WHY

Reducer runs for every action.

βœ… Fix

Split reducers:
combineReducers(...)

🧠 Best Practice

Keep reducers small and focused.

11. Returning New Object Unnecessarily

🐞 Buggy Code

return { ...state };

❌ What’s Wrong

Triggers unnecessary re-render.

πŸ€” WHY

New reference even if no change.

βœ… Fix

return state;

🧠 Best Practice

Return same reference if unchanged.

12. Using Random Values in Reducer

🐞 Buggy Code

case "generate":
  return { value: Math.random() };

❌ What’s Wrong

Non-deterministic reducer.

πŸ€” WHY

Breaks predictability.

βœ… Fix

Generate outside reducer.

🧠 Best Practice

Reducers must be deterministic.

13. Forgetting to Handle Edge Action

🐞 Buggy Code

case "remove":
  return state.filter(item => item.id !== action.id);

❌ What’s Wrong

Fails if action.id undefined.

πŸ€” WHY

No validation.

βœ… Fix

if (!action.id) return state;

🧠 Best Practice

Validate action payloads.

14. Using Reducer for Simple State

🐞 Buggy Code

const [state, dispatch] = useReducer(
  (s, a) => ({ value: a }),
  { value: 0 }
);

❌ What’s Wrong

Over-engineered.

πŸ€” WHY

No complex logic.

βœ… Fix

Use useState.

🧠 Best Practice

Choose the right abstraction.

15. State Reset Bug

🐞 Buggy Code

case "reset":
  return initialState;

❌ What’s Wrong

initialState might be stale or mutated.

πŸ€” WHY

Shared reference issues.

βœ… Fix

return { ...initialState };

🧠 Best Practice

Avoid shared mutable references.

16. Dispatch Inside Loop

🐞 Buggy Code

items.forEach(() => dispatch({ type: "update" }));

❌ What’s Wrong

Multiple re-renders.

πŸ€” WHY

Each dispatch triggers update.

βœ… Fix

Batch updates or single action.

🧠 Best Practice

Minimize dispatch frequency.

17. Ignoring Action Payload Shape

🐞 Buggy Code

case "add":
  return [...state, action.payload.text];

❌ What’s Wrong

Assumes payload shape blindly.

πŸ€” WHY

Runtime crashes if payload invalid.

βœ… Fix

Validate payload.

🧠 Best Practice

Use type-safe patterns.

πŸ”š Final Thought

These bugs reflect real production issues:
  • Identity vs mutation
  • Concurrency assumptions
  • Performance pitfalls
  • Architectural misuse

🧠 Real-World Machine Coding Problems using useReducer (Senior-Level)


1. Advanced E-commerce Cart System

🧩 Requirements

  • Add/remove/update items
  • Support discounts, coupons, taxes
  • Multi-currency support
  • Persist cart (localStorage)

πŸ–₯️ UI Behavior

  • Live updates on quantity change
  • Coupon apply/remove UI
  • Price breakdown (subtotal, tax, total)

πŸ”„ State Flow

  • Actions: ADD_ITEM, REMOVE_ITEM, APPLY_COUPON, SET_CURRENCY
  • Derived values computed inside reducer

⚠️ Edge Cases

  • Invalid coupon
  • Currency switch recalculation
  • Negative totals

⚑ Performance

  • Avoid recalculating totals on every render
  • Memoize currency conversions

πŸ—οΈ Architecture

  • Single reducer with normalized state
  • Optional split: pricingReducer, cartReducer

🧠 Approach

  1. Define normalized state (itemsById)
  2. Handle item merge logic
  3. Compute totals inside reducer
  4. Sync with storage via useEffect

2. Collaborative Kanban Board (Trello-like)

🧩 Requirements

  • Drag/drop cards across columns
  • Real-time updates (simulate via actions)
  • Add/edit/delete cards

πŸ–₯️ UI Behavior

  • Smooth drag animations
  • Column-wise grouping

πŸ”„ State Flow

{
  columns: { columnId: [cardIds] },
  cards: { cardId: { title, description } }
}

⚠️ Edge Cases

  • Dropping in same position
  • Missing card references

⚑ Performance

  • Avoid re-rendering entire board
  • Use memoized selectors

πŸ—οΈ Architecture

  • Split reducers: boardReducer, cardReducer

🧠 Approach

  1. Normalize data
  2. Handle MOVE_CARD action carefully
  3. Ensure immutability for lists

3. Real-Time Chat Application

🧩 Requirements

  • Send/receive messages
  • Typing indicators
  • Message status (sent/read)

πŸ–₯️ UI Behavior

  • Scroll to latest message
  • Show typing users

πŸ”„ State Flow

{
  messages: [],
  typingUsers: [],
  status: {}
}

⚠️ Edge Cases

  • Duplicate messages
  • Out-of-order delivery

⚑ Performance

  • Limit message buffer
  • Virtualize message list

πŸ—οΈ Architecture

  • messageReducer + uiReducer

🧠 Approach

  1. Deduplicate messages
  2. Maintain message IDs
  3. Handle status updates

4. Form Builder (Dynamic + Conditional Logic)

🧩 Requirements

  • Add/remove fields dynamically
  • Conditional visibility (if X β†’ show Y)
  • Validation rules

πŸ–₯️ UI Behavior

  • Fields appear/disappear dynamically

πŸ”„ State Flow

  • fields config + values + errors

⚠️ Edge Cases

  • Circular dependencies
  • Hidden field validation

⚑ Performance

  • Avoid recalculating all conditions

πŸ—οΈ Architecture

  • Separate config vs runtime state

🧠 Approach

  1. Store field schema
  2. Evaluate conditions in reducer
  3. Trigger validations on change

5. Infinite Scroll Feed (Social Media)

🧩 Requirements

  • Fetch paginated data
  • Merge results
  • Handle loading/error

πŸ–₯️ UI Behavior

  • Scroll triggers fetch
  • Loader at bottom

πŸ”„ State Flow

{
  items: [],
  page: 1,
  status: "idle"
}

⚠️ Edge Cases

  • Duplicate pages
  • Rapid scroll triggering

⚑ Performance

  • Debounce fetch
  • Prevent duplicate requests

πŸ—οΈ Architecture

  • fetchReducer (FSM-like)

🧠 Approach

  1. Track page state
  2. Append new data
  3. Prevent parallel fetches

6. Multi-Tab Session Manager

🧩 Requirements

  • Manage multiple tabs (like browser tabs)
  • Persist tab state
  • Restore sessions

πŸ–₯️ UI Behavior

  • Switch tabs instantly
  • Close/open tabs

πŸ”„ State Flow

{ tabs: [], activeTabId }

⚠️ Edge Cases

  • Closing active tab
  • Duplicate tab IDs

⚑ Performance

  • Avoid re-rendering inactive tabs

πŸ—οΈ Architecture

  • tabReducer + contentReducer

🧠 Approach

  1. Maintain tab registry
  2. Handle active switching
  3. Persist in storage

7. Advanced Filter Engine (E-commerce/Search)

🧩 Requirements

  • Multi-select filters
  • Range filters
  • Sort options

πŸ–₯️ UI Behavior

  • Real-time filtering

πŸ”„ State Flow

{ filters, sort, results }

⚠️ Edge Cases

  • Conflicting filters
  • Empty results

⚑ Performance

  • Memoize filtering logic

πŸ—οΈ Architecture

  • filterReducer + dataReducer

🧠 Approach

  1. Store raw data separately
  2. Apply filters in reducer or selector
  3. Optimize with memoization

8. Notification Center with Priorities

🧩 Requirements

  • Queue notifications
  • Priority-based ordering
  • Auto-dismiss

⚠️ Edge Cases

  • Duplicate notifications
  • Expired notifications

⚑ Performance

  • Limit queue size

🧠 Approach

  • Priority queue logic in reducer

9. File Upload Dashboard

🧩 Requirements

  • Upload multiple files
  • Track progress
  • Retry/cancel

πŸ”„ State Flow

files: [{ id, progress, status }]

⚠️ Edge Cases

  • Network failure
  • Partial uploads

⚑ Performance

  • Avoid updating entire list

🧠 Approach

  • Update specific file entry

10. Undo/Redo Rich Text Editor

🧩 Requirements

  • Full history tracking
  • Keyboard shortcuts

⚠️ Edge Cases

  • Large history memory

⚑ Performance

  • Limit history size

🧠 Approach

  • past/present/future structure

11. Permissions Management Dashboard

🧩 Requirements

  • Assign roles
  • Toggle permissions

⚠️ Edge Cases

  • Conflicting rules

🧠 Approach

  • Normalize roles + permissions

12. Scheduling Calendar (Google Calendar-like)

🧩 Requirements

  • Add/edit events
  • Handle overlaps

⚠️ Edge Cases

  • Time conflicts

⚑ Performance

  • Efficient time lookup

🧠 Approach

  • Store events indexed by date

13. Shopping Checkout Flow (Multi-step + Validation)

🧩 Requirements

  • Steps: Address β†’ Payment β†’ Review
  • Validation per step

⚠️ Edge Cases

  • Partial completion

🧠 Approach

  • FSM-like reducer

14. Data Grid with Inline Editing

🧩 Requirements

  • Edit rows inline
  • Bulk updates

⚠️ Edge Cases

  • Validation errors

⚑ Performance

  • Row-level updates

🧠 Approach

  • Normalize rows by ID

15. Real-Time Stock Dashboard

🧩 Requirements

  • Live price updates
  • Highlight changes

⚠️ Edge Cases

  • Rapid updates

⚑ Performance

  • Throttle updates

🧠 Approach

  • Update only changed stocks

16. Feature Flag System

🧩 Requirements

  • Enable/disable features dynamically

⚠️ Edge Cases

  • Dependency between flags

🧠 Approach

  • Reducer enforces rules

πŸ”š Final Insight

These problems test architectural thinking, not just coding:
  • State normalization
  • Reducer composition
  • Performance optimization
  • Edge-case handling
  • Real-world constraints

🧠 Senior-Level Interview Questions β€” useReducer


1. When would you choose useReducer over useState in a real production system?

πŸ” Follow-up

  • What signals tell you state is β€œcomplex enough”?
  • Can you refactor from useState to useReducer safely?

βœ… Strong Answer

Use useReducer when:
  • State transitions are interdependent
  • Multiple state variables must change atomically
  • Logic is reused or needs testability
Example:
dispatch({ type: "CHECKOUT_SUCCESS", payload })

❌ Weak Answer

β€œWhen state is complex.”
πŸ‘‰ Fails because it doesn’t define what complexity means or give criteria.

2. How does React decide whether to re-render after a reducer update?

πŸ” Follow-up

  • What happens if you mutate state?
  • Does React do deep comparison?

βœ… Strong Answer

React uses reference equality (Object.is). If reducer returns the same reference β†’ no re-render.

❌ Weak Answer

β€œReact checks if values changed.”
πŸ‘‰ Incorrect β€” React does NOT deep compare.

3. Explain why reducers must be pure, especially in React 18+.

πŸ” Follow-up

  • What breaks in concurrent rendering?
  • Give a real bug example

βœ… Strong Answer

Reducers may be re-run or replayed in concurrent rendering. Impure reducers cause:
  • Duplicate API calls
  • Inconsistent state

❌ Weak Answer

β€œBecause Redux says so.”
πŸ‘‰ No understanding of React’s execution model.

4. Design a global state system using useReducer and useContext. What are the trade-offs?

πŸ” Follow-up

  • How do you prevent unnecessary re-renders?
  • When would you switch to Redux/Zustand?

βœ… Strong Answer

  • Use reducer for logic, context for distribution
  • Split contexts or memoize value
Trade-offs:
  • Simpler than Redux
  • But causes full tree re-renders

❌ Weak Answer

β€œIt replaces Redux.”
πŸ‘‰ Oversimplification β€” ignores scaling issues.

5. How would you debug a reducer that behaves inconsistently in production but not locally?

πŸ” Follow-up

  • What tools/strategies?
  • What assumptions would you question?

βœ… Strong Answer

  • Log actions + state transitions
  • Check for impure logic
  • Validate concurrency assumptions

❌ Weak Answer

β€œAdd console logs.”
πŸ‘‰ Too shallow β€” no strategy.

6. What are the performance implications of large reducers?

πŸ” Follow-up

  • How would you optimize?
  • When to split reducers?

βœ… Strong Answer

Reducers run on every dispatch β†’ large logic slows updates. Optimize via:
  • Splitting reducers
  • Memoized selectors

❌ Weak Answer

β€œReducers are fast.”
πŸ‘‰ Ignores scale.

7. How would you model a finite state machine using useReducer?

πŸ” Follow-up

  • How do you prevent invalid transitions?

βœ… Strong Answer

Use state + allowed transitions:
if (state.status === "loading" && action.type === "SUCCESS")
Ensures controlled transitions

❌ Weak Answer

β€œUse switch case.”
πŸ‘‰ Misses FSM concept.

8. What is a subtle bug caused by returning a new object unnecessarily?

πŸ” Follow-up

  • How does this impact performance?

βœ… Strong Answer

Triggers unnecessary re-renders:
return { ...state }; // even if unchanged

❌ Weak Answer

β€œNo issue.”
πŸ‘‰ Misses rendering implications.

9. How would you handle async flows with useReducer?

πŸ” Follow-up

  • Why not inside reducer?
  • Compare with Redux middleware

βœ… Strong Answer

Use useEffect to dispatch lifecycle actions:
dispatch({ type: "FETCH_START" });
Reducer remains pure.

❌ Weak Answer

β€œPut fetch in reducer.”
πŸ‘‰ Violates purity.

10. How do you avoid unnecessary re-renders when using useReducer with Context?

πŸ” Follow-up

  • What patterns help?

βœ… Strong Answer

  • Split contexts
  • Memoize provider value
  • Use selector-based consumption

❌ Weak Answer

β€œUse memo.”
πŸ‘‰ Too vague.

11. What are the trade-offs of normalizing state in reducers?

πŸ” Follow-up

  • When is normalization overkill?

βœ… Strong Answer

Pros:
  • Easier updates
  • Avoid deep nesting
Cons:
  • More complex structure

❌ Weak Answer

β€œAlways normalize.”
πŸ‘‰ Not always necessary.

12. Explain a real-world scenario where useReducer improves debugging.

πŸ” Follow-up

  • How would you log actions?

βœ… Strong Answer

Action-based updates β†’ easier tracing:
console.log(action)

❌ Weak Answer

β€œIt’s easier.”
πŸ‘‰ No concrete reasoning.

13. How does batching affect multiple dispatch calls?

πŸ” Follow-up

  • Does React skip any updates?

βœ… Strong Answer

React batches but processes all actions sequentially.

❌ Weak Answer

β€œOnly last dispatch runs.”
πŸ‘‰ Incorrect.

14. What’s the risk of deeply nested state in reducers?

πŸ” Follow-up

  • How would you refactor?

βœ… Strong Answer

  • Verbose updates
  • Error-prone
Solution:
  • Normalize state
  • Split reducers

❌ Weak Answer

β€œIt’s fine.”
πŸ‘‰ Ignores maintainability.

15. How would you design undo/redo using useReducer?

πŸ” Follow-up

  • What data structure?

βœ… Strong Answer

Use:
{ past: [], present: {}, future: [] }

❌ Weak Answer

β€œStore previous state.”
πŸ‘‰ Too vague.

16. When does useReducer become an anti-pattern?

πŸ” Follow-up

  • Give real examples

βœ… Strong Answer

  • Simple toggles
  • Independent state

❌ Weak Answer

β€œNever.”
πŸ‘‰ Shows poor judgment.

17. How does dispatch stability influence component design?

πŸ” Follow-up

  • Do you need useCallback?

βœ… Strong Answer

Dispatch is stable β†’ safe to pass down.

❌ Weak Answer

β€œWrap in useCallback.”
πŸ‘‰ Unnecessary optimization.

πŸ” Follow-up

  • How to fix?

βœ… Strong Answer

Using outdated state outside reducer logic. Fix with:
  • Proper dependencies
  • Moving logic into reducer

❌ Weak Answer

β€œNot sure.”
πŸ‘‰ Misses real-world issue.

19. How would you scale useReducer in a large application?

πŸ” Follow-up

  • When to move to external state library?

βœ… Strong Answer

  • Split reducers
  • Use context layering
  • Move to Redux/Zustand when:
    • Cross-cutting state grows

❌ Weak Answer

β€œJust use one reducer.”
πŸ‘‰ Not scalable.

20. What mental model do you use when designing reducers?

πŸ” Follow-up

  • How do you ensure correctness?

βœ… Strong Answer

Think of reducer as:
State machine with explicit transitions
Ensures predictability and testability.

❌ Weak Answer

β€œJust switch case logic.”
πŸ‘‰ Lacks abstraction.

πŸ”š Final Insight

A strong candidate demonstrates:
  • State modeling skills
  • Understanding of React internals
  • Trade-off awareness
  • Debugging mindset
Not just β€œhow to use useReducer”, but:
When, why, and how it behaves under real-world constraints.