React Redux


React Redux Interview with follow-up questions

1. What is Redux and why is it used in React?

Redux is a predictable state-management library providing a single, centralized store for application state. It's used with React (via react-redux) when state is shared widely and changes in complex ways.

Core ideas: state is a single immutable object tree; you change it only by dispatching actions; pure reducers compute the next state. This makes state changes predictable, traceable, and easy to debug (time-travel via Redux DevTools).

Important modern guidance: the recommended way to use Redux today is Redux Toolkit (RTK) — the official, batteries-included toolset. It eliminates the old boilerplate with configureStore, createSlice (auto-generates actions + reducers, lets you write "mutating" logic safely via Immer), and RTK Query for data fetching/caching.

import { createSlice, configureStore } from "@reduxjs/toolkit";
const counter = createSlice({
  name: "counter",
  initialState: { value: 0 },
  reducers: { increment: (s) => { s.value += 1; } },
});
export const store = configureStore({ reducer: { counter: counter.reducer } });

Note that many apps no longer need Redux at all: local state + Context, or lighter stores like Zustand/Jotai, and TanStack Query for server state, cover a lot of cases. Reach for Redux when you have substantial, complex client state.

↑ Back to top

Follow-up 1

Can you explain the concept of a single source of truth in Redux?

In Redux, the concept of a single source of truth means that the entire state of an application is stored in a single JavaScript object called the 'store'. This store represents the state of the application and is the single source of truth for the entire application. Any changes to the state must be done through actions, which are dispatched to the store. This ensures that the state is always updated in a predictable and consistent manner.

Follow-up 2

How does Redux help in managing the application state?

Redux helps in managing the application state by providing a centralized store that holds the state of the application. Any component in the application can access the state from the store and update it by dispatching actions. This makes it easier to manage and update the state across different components, as all the state changes are handled in a predictable and consistent manner. Redux also provides middleware, such as Redux Thunk or Redux Saga, which allows for handling asynchronous actions and side effects in a more organized way.

Follow-up 3

What are the core principles of Redux?

The core principles of Redux are:

  1. Single source of truth: The entire state of an application is stored in a single JavaScript object called the 'store'.

  2. State is read-only: The state in Redux is immutable, meaning it cannot be directly modified. Any changes to the state must be done through actions.

  3. Changes are made with pure functions: Redux uses pure functions called reducers to specify how the state should be updated in response to actions.

  4. Changes are made with pure functions: Redux uses pure functions called reducers to specify how the state should be updated in response to actions.

  5. Time-travel debugging: Redux provides a feature called time-travel debugging, which allows developers to replay actions and see how the state changes over time. This can be very helpful for debugging and understanding the application's behavior.

2. Can you explain the concept of actions in Redux?

An action is a plain JavaScript object describing what happened. It must have a type (a string identifier) and may carry a payload with any data the reducer needs:

{ type: "todos/added", payload: { id: 1, text: "Learn RTK" } }

Actions are the only way to change the store — you dispatch them, and reducers respond. Action creators are functions that return actions, so you don't hand-write objects everywhere.

With Redux Toolkit (the modern standard) you rarely write actions or action creators by hand — createSlice generates them for you from your reducer names:

const slice = createSlice({
  name: "todos",
  initialState: [],
  reducers: { added: (state, action) => { state.push(action.payload); } },
});
slice.actions.added({ id: 1, text: "..." }); // auto-created action creator
// → { type: "todos/added", payload: {...} }

So conceptually actions are unchanged (typed event objects), but in practice RTK creates them for you with consistent slice/action type names.

↑ Back to top

Follow-up 1

How do you define action creators in Redux?

Action creators in Redux are functions that create and return action objects. They encapsulate the logic for creating actions and can also accept parameters to customize the action payload. Action creators are typically defined as separate functions and are called when an action needs to be dispatched. Here's an example of an action creator in Redux:

function incrementCounter(value) {
  return {
    type: 'INCREMENT',
    payload: value
  };
}

Follow-up 2

What is the significance of the type property in an action?

The type property in an action is a string that describes the type of action being performed. It is a required property and is used by the reducers to determine how to update the state of the application. By convention, the type property is usually defined as an uppercase string constant, such as 'INCREMENT' or 'FETCH_DATA', to make it easier to identify and handle different types of actions.

Follow-up 3

How are actions dispatched in Redux?

Actions in Redux are dispatched using the dispatch function provided by the Redux store. The dispatch function takes an action object as its argument and sends it to the store. The store then forwards the action to the reducers, which update the state based on the action type. Here's an example of dispatching an action in Redux:

store.dispatch({
  type: 'INCREMENT',
  payload: 1
});

3. What are reducers in Redux and how do they work?

A reducer is a pure function (state, action) => newState that specifies how the store changes in response to an action. Rules: it must be pure (no side effects, no mutation, no async) and must not mutate existing state — it returns a new state object.

Classic form:

function counterReducer(state = 0, action) {
  switch (action.type) {
    case "INCREMENT": return state + 1;
    case "DECREMENT": return state - 1;
    default:          return state;
  }
}

With Redux Toolkit (recommended today), createSlice lets you write logic that looks mutating — Immer safely turns it into immutable updates under the hood:

const counter = createSlice({
  name: "counter",
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; },  // safe: Immer produces new state
    decrement: (state) => { state.value -= 1; },
  },
});

This removes the most error-prone part of classic Redux (manual immutable spreads) while keeping reducers conceptually pure.

↑ Back to top

Follow-up 1

Can you explain the concept of pure functions in the context of reducers?

In the context of reducers, pure functions are functions that always return the same output for the same input, and do not have any side effects. This means that given the same state and action, a pure reducer function will always produce the same new state. Pure functions do not modify the original state, but instead create a new state object with the updated values. This immutability ensures that the state remains predictable and makes it easier to track changes.

Here's an example of a pure reducer function:

function counterReducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

Follow-up 2

How does a reducer handle an action in Redux?

In Redux, a reducer handles an action by using a switch statement to determine how the state should be updated based on the action's type. The action object typically has a type property that describes the type of action being performed. The reducer function then checks the action's type and returns a new state based on the type.

Here's an example of how a reducer handles an action in Redux:

function counterReducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

Follow-up 3

What is the role of the initial state in a reducer?

The initial state in a reducer defines the initial value of the state before any actions are dispatched. It is specified as the default value for the state parameter in the reducer function's signature. When the reducer is called for the first time, if the state parameter is not provided (i.e., it is undefined), the initial state value will be used.

Here's an example of a reducer with an initial state:

const initialState = {
  count: 0
};

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

4. How do you handle asynchronous operations in Redux?

Reducers must be synchronous and pure, so async work (API calls) is handled via middleware or thunks. The modern, recommended approach uses Redux Toolkit:

createAsyncThunk generates pending/fulfilled/rejected actions for you:

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

const fetchUser = createAsyncThunk("users/fetch", async (id) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
});

const usersSlice = createSlice({
  name: "users",
  initialState: { data: null, loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending,   (s) => { s.loading = true; })
      .addCase(fetchUser.fulfilled, (s, a) => { s.loading = false; s.data = a.payload; })
      .addCase(fetchUser.rejected,  (s, a) => { s.loading = false; s.error = a.error.message; });
  },
});

Even better for server data: RTK Query handles fetching, caching, loading/error states, and re-fetching declaratively, removing most of this boilerplate.

Older approaches you may still encounter: redux-thunk (now bundled with RTK by default) and redux-saga (generator-based, for complex async flows). For purely server state, many teams use TanStack Query instead of Redux.

↑ Back to top

Follow-up 1

What is Redux Thunk?

Redux Thunk is a middleware for Redux that allows you to write action creators that return functions instead of plain objects. These functions can be asynchronous and have access to the Redux store's dispatch method. Redux Thunk is commonly used to handle asynchronous operations in Redux, such as making API calls. Here's an example of how to use Redux Thunk:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const initialState = {
  loading: false,
  data: null,
  error: null
};

const reducer = (state = initialState, action) => {
  // reducer logic
};

const fetchData = () => {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_DATA_REQUEST' });
    try {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'FETCH_DATA_FAILURE', payload: error.message });
    }
  };
};

const store = createStore(reducer, applyMiddleware(thunk));

store.dispatch(fetchData());

In this example, the fetchData action creator returns an async function that dispatches different actions based on the success or failure of the API call. Redux Thunk middleware allows us to use this async function as an action.

Follow-up 2

How does Redux Saga help in handling side effects?

Redux Saga is a middleware for Redux that helps in handling side effects, such as asynchronous operations, in a more declarative and testable way. It uses a combination of ES6 generators and Redux's middleware to allow you to write complex asynchronous logic in a synchronous style. Here's an example of how to use Redux Saga:

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { takeEvery, put, call } from 'redux-saga/effects';

const initialState = {
  loading: false,
  data: null,
  error: null
};

const reducer = (state = initialState, action) => {
  // reducer logic
};

function* fetchData() {
  try {
    yield put({ type: 'FETCH_DATA_REQUEST' });
    const response = yield call(fetch, 'https://api.example.com/data');
    const data = yield call([response, 'json']);
    yield put({ type: 'FETCH_DATA_SUCCESS', payload: data });
  } catch (error) {
    yield put({ type: 'FETCH_DATA_FAILURE', payload: error.message });
  }
}

function* watchFetchData() {
  yield takeEvery('FETCH_DATA', fetchData);
}

const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchFetchData);

store.dispatch({ type: 'FETCH_DATA' });

In this example, the fetchData generator function is responsible for handling the asynchronous logic. It uses the put effect to dispatch actions, the call effect to call functions, and the takeEvery effect to listen for specific actions. Redux Saga middleware allows us to run this generator function and handle side effects in a more controlled manner.

Follow-up 3

Can you explain the concept of middleware in Redux?

Middleware in Redux is a way to extend the behavior of the dispatch function. It sits between the action being dispatched and the reducer that updates the state. Middleware can intercept actions, modify them, or perform additional tasks before they reach the reducer. This allows you to add extra functionality to Redux, such as handling asynchronous operations, logging, or routing. Here's an example of how to create and use middleware in Redux:

import { createStore, applyMiddleware } from 'redux';

const initialState = {
  // initial state
};

const reducer = (state = initialState, action) => {
  // reducer logic
};

const loggerMiddleware = (store) => (next) => (action) => {
  console.log('Dispatching action:', action);
  const result = next(action);
  console.log('Next state:', store.getState());
  return result;
};

const store = createStore(reducer, applyMiddleware(loggerMiddleware));

store.dispatch({ type: 'SOME_ACTION' });

In this example, the loggerMiddleware intercepts actions and logs information before and after the action is dispatched. It then calls the next function to pass the action to the next middleware or the reducer. Middleware can be composed by chaining multiple functions together using the applyMiddleware function from Redux.

5. How do you connect a React component to the Redux store?

The modern way is the hooks API from react-redux: useSelector to read state and useDispatch to dispatch actions. First wrap your app in ``.

import { Provider, useSelector, useDispatch } from "react-redux";
import { store } from "./store";
import { increment } from "./counterSlice";

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  return  dispatch(increment())}>{count};
}

function App() {
  return (



  );
}

Notes:

  • useSelector re-renders the component when its selected slice changes; keep selectors narrow and consider memoized selectors (createSelector/RTK's createSelector) for derived data.
  • With TypeScript, create typed useAppSelector/useAppDispatch hooks.

The older connect(mapStateToProps, mapDispatchToProps)(Component) HOC still works and appears in legacy code, but the hooks API is the recommended approach for new components.

↑ Back to top

Follow-up 1

What is the role of the mapStateToProps function?

The mapStateToProps function is a function that is used to map the Redux store state to the props of a connected component. It takes the current state of the Redux store as an argument and returns an object with the props that you want to pass to your component. These props will be updated whenever the state of the Redux store changes. Here's an example of how to use mapStateToProps:

const mapStateToProps = (state) => {
  return {
    prop1: state.someValue,
    prop2: state.anotherValue
  };
};

Follow-up 2

What is the role of the mapDispatchToProps function?

The mapDispatchToProps function is a function that is used to map action creators to the props of a connected component. It takes the dispatch function as an argument and returns an object with the action creators that you want to pass to your component. These action creators will be wrapped in the dispatch function, so you can call them directly in your component. Here's an example of how to use mapDispatchToProps:

import { incrementCounter, decrementCounter } from './actions';

const mapDispatchToProps = (dispatch) => {
  return {
    increment: () => dispatch(incrementCounter()),
    decrement: () => dispatch(decrementCounter())
  };
};

Follow-up 3

Can you explain the concept of higher-order components in the context of the connect function?

In the context of the connect function, higher-order components (HOCs) are functions that take a component and return a new component with additional props or behavior. The connect function itself is a higher-order function that returns a new connected component. It takes two optional arguments: mapStateToProps and mapDispatchToProps. These arguments are functions that define how to map the Redux store state and action creators to the props of the connected component. The connected component returned by connect will have access to the props defined in mapStateToProps and mapDispatchToProps, as well as the props passed to it from its parent component. Here's an example of how to use the connect function as a higher-order component:

import { connect } from 'react-redux';

const MyComponent = (props) => {
  // component code
};

const mapStateToProps = (state) => {
  // return an object with the props you want to pass to your component
};

const mapDispatchToProps = (dispatch) => {
  // return an object with the action creators you want to pass to your component
};

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

Live mock interview

Mock interview: React Redux

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.