Top 50 React Js Interview questions for experienced Developer

1. What are the advantages of using Redux with React?

Redux centralizes application state in a single predictable store, making state flow explicit and easier to debug. It improves maintainability in large apps by decoupling UI from state logic, enables time-travel debugging, simplifies testing, and provides a clear place to apply middleware for logging, analytics, or async flows.

import { createStore } from 'redux';
const store = createStore(rootReducer);

Tip: Use Redux for complex state with many interactions or shared state across distant components; otherwise prefer local state or Context for simplicity.

2. What is the role of the Provider component in Redux?

Provider makes the Redux store available to the React component tree so any nested component can read state or dispatch actions via hooks or connect. It prevents prop-drilling and keeps components agnostic of the store’s implementation.

import { Provider } from 'react-redux';
<Provider store={store}><App /></Provider>

Best practice: Wrap the highest-level component (often at index.js) and avoid creating multiple stores unless intentionally isolating features.

3. How does useSelector work?

useSelector subscribes a component to the Redux store and returns a selected slice of state. It triggers re-renders only when the selected value changes by reference, so write focused selectors to minimize updates.

const data = useSelector(state => state.data);

Performance: Memoize expensive derived data with Reselect to avoid unnecessary recalculation and re-renders.

4. What is an action creator?

An action creator is a function that returns an action object ({ type, payload }). It centralizes action creation, improves readability, and simplifies testing and reuse. Async action creators (thunks) return functions that perform side effects.

const updateValue = val => ({ type: 'UPDATE', payload: val });

Note: With RTK, action creators are generated automatically from slices.

5. How do you handle async code in Redux?

Use middleware to manage async flows. redux-thunk lets action creators dispatch functions. redux-saga offers generator-based side-effect handling. Redux Toolkit (RTK) includes thunks and RTK Query for declarative data fetching and caching.

// thunk example
dispatch(fetchDataAsync());

Recommendation: Use RTK Query for standard CRUD and caching; use sagas for complex orchestrations.

6. What does a reducer do?

A reducer is a pure function that returns new state based on the previous state and an action. Reducers must not mutate state directly to keep changes predictable, allowing time-travel debugging and easy testing.

function reducer(state = {}, action) {
  switch(action.type) { /* ... */ }
  return state;
}

Tip: Split reducers by feature and compose them with combineReducers.

7. What are selectors in Redux?

Selectors encapsulate logic for reading and deriving data from state. They improve reuse, hide state shape, and, when memoized (Reselect), avoid unnecessary recalculations and re-renders.

const getActiveUsers = state => state.users.filter(u => u.active);

Pattern: Keep selectors colocated with slices or feature modules for maintainability.

8. How does Redux Toolkit simplify Redux?

Redux Toolkit (RTK) removes boilerplate by providing configureStore, createSlice, and built-in middleware. It uses Immer for immutable updates, sets sane defaults, and integrates RTK Query for data fetching and caching.

import { createSlice } from '@reduxjs/toolkit';
const slice = createSlice({ name: 'auth', initialState, reducers });

Advice: RTK is the recommended way to write modern Redux code.

9. What is immutability in Redux?

Immutability means never changing the existing state object; always return a new state. This enables straightforward change detection, predictable updates, and compatibility with DevTools. Use spread operators or Immer (built into RTK) to write concise immutable updates.

return [...state, newItem];

Warning: Mutating nested objects can cause subtle bugs; prefer immutability helpers or Immer.

10. How does useDispatch help in Redux?

useDispatch returns the store’s dispatch function for functional components so they can dispatch actions or thunks. Keep business logic outside components and dispatch high-level actions to keep components focused on UI.

const dispatch = useDispatch();
dispatch({ type: 'LOGIN' });

Pattern: Use action creators and thunks to encapsulate side effects.

11. How can Redux DevTools help developers?

Redux DevTools allow inspecting action history, state snapshots, and time-travel debugging. They simplify tracking regressions and analyzing performance bottlenecks in reducers and middleware.

window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();

Tip: Use DevTools during development and disable or limit them in production builds.

12. What does combineReducers do?

combineReducers merges multiple slice reducers into a single root reducer. Each slice handles its own subtree, keeping logic modular and easy to reason about.

const rootReducer = combineReducers({ auth, posts });

Best practice: Keep slice state shallow and delegating nested logic to sub-slices when needed.

13. How do you structure a large Redux project?

Organize by feature: colocate components, slices, selectors, and tests in a feature folder. Use a central /src/store with clear entry points. Adopt conventions like Ducks or Feature-Sliced architecture for scalable, testable code.

/src/store
/src/features/user/userSlice.js
/src/features/user/UserComponent.jsx

Pro tip: Export a minimal slice API (actions, selectors) to limit coupling between features.

14. What is middleware in Redux?

Middleware intercepts actions between dispatch and reducers to handle side effects (logging, analytics, async calls, routing). Middleware is composable and executed in the order applied to the store.

const logger = store => next => action => {
  console.log('dispatching', action);
  return next(action);
};

Use case: Add middleware for authorization checks, metrics collection, or standardized API handling.

15. What are thunks?

Thunks are functions returned by action creators (redux-thunk) that receive dispatch and getState. They enable async calls, conditional dispatches, and encapsulation of side effects outside reducers.

const fetchUser = () => async dispatch => {
  const user = await getUser();
  dispatch({ type: 'SET_USER', payload: user });
};

Alternative: Use RTK Query for standard data fetching needs to reduce repetitive thunk code.

16. How is Redux different from the Context API?

React’s Context API shares values across the tree and is suited for small, static global values. Redux is a full state container with middleware, immutable patterns, and tooling for complex state, side effects, and large-scale apps. For frequent, complex updates or global business logic, Redux scales better.

// Context vs Redux example
const data = useSelector(state => state.data);
const ctx = useContext(MyContext);

Rule of thumb: Use Context for theming or locale; use Redux for complex application state.

17. How do you test Redux logic?

Test reducers as pure functions with given actions and expected state. For integration, use a mock store to assert dispatched actions and use React Testing Library to render components with a test Provider. Mock network requests with MSW or Jest to test thunks or sagas.

render(<Provider store={mockStore}><MyComponent /></Provider>);

Tip: Write unit tests for selectors and slice reducers separately for fast, reliable feedback.

18. What are slices in Redux Toolkit?

A slice (RTK) groups reducer logic, initial state, and generated action creators for a single feature using createSlice. Slices reduce boilerplate and integrate Immer to write mutable-looking code that produces immutable updates.

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: { login: (state, action) => { state.user = action.payload; } }
});

Organize: Keep one slice per logical feature and export selectors and actions from the slice file.

19. What is the difference between mapStateToProps and useSelector?

mapStateToProps is used with connect for class or legacy components and maps state to props. useSelector is a hook for function components that selects state directly and often results in simpler code and finer-grained subscriptions.

// connect(mapStateToProps)(Component)  vs  const value = useSelector(state => state.value)

Migration: Prefer hooks for new code; connect remains useful for performance-critical components.

20. What is the Redux store’s main responsibility?

The store holds the entire application state tree, exposes methods to read state (getState()), update state (dispatch()), and subscribe to state changes (subscribe()). It orchestrates reducers and middleware to produce predictable state transitions.

import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({ reducer: rootReducer });

Security note: Never store sensitive information (passwords, tokens without encryption) in the Redux store; use secure storage for secrets and limit lifetime of tokens.

21. What is RTK Query and when should you use it?

RTK Query is a data-fetching and caching solution built into Redux Toolkit. It simplifies API calls, caching, automatic refetching, and cache invalidation with minimal boilerplate. Use it for standard CRUD operations where caching, deduplication, and pagination are needed.

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: () => ({}) });

Tip: Prefer RTK Query over custom thunks for most REST interactions.

22. How does optimistic updates work in Redux?

Optimistic updates update UI immediately before the server confirms the change, improving perceived performance. Implement by dispatching a local update action, performing the API call, then rolling back on failure.

dispatch(itemAddedLocally(item));
try { await api.add(item); } catch { dispatch(rollbackAdd(item.id)); }

Warning: Carefully handle conflicts and ensure rollback paths are robust.

23. How do you implement pagination with Redux?

Store pagination state (page, pageSize, total) in Redux and fetch pages via thunks or RTK Query. Keep normalized data by id and maintain an index of IDs per page to avoid duplication.

state.pagination = { page: 1, idsByPage: {1: [1,2,3]} };

Best practice: Reuse cached pages and invalidate only affected pages on updates.

24. What is normalized state and why use it?

Normalized state stores entities separately (by id) and keeps references in arrays. This avoids duplication, simplifies updates, and improves performance for large datasets.

state = { users: { byId: {1:{...}}, allIds: [1] } };

Tool: Use normalizr or write simple normalization utilities in transforms.

25. How do you handle file uploads in Redux?

Perform uploads in thunks or middleware, dispatch progress actions, store upload metadata in state, and avoid storing binary data in the store. Use presigned URLs for large files to offload traffic.

dispatch(startUpload(id)); await api.upload(file, onProgress); dispatch(uploadComplete(id));

Note: Keep only metadata and status in Redux, not file blobs.

26. How to implement role-based access control (RBAC) with Redux?

Store user roles/permissions in auth slice. Use selectors to compute allowed actions and guard UI or route access based on permissions.

const canEdit = useSelector(state => state.auth.roles.includes('editor'));

Security: Always enforce permissions on the backend as well.

27. How do you handle form state with Redux?

Prefer local component state for simple forms. For complex multi-step forms or shared form data, keep form state in Redux or use libraries like Redux Form or React Final Form. Alternatively, use RTK to store validated snapshots.

dispatch(formSlice.actions.updateField({ name, value }));

Tip: Avoid storing transient UI state globally unless needed.

28. How do you approach caching strategies in Redux?

Use cache-first for stable data, network-first for critical fresh data, and stale-while-revalidate for UX balance. RTK Query provides built-in caching and invalidation primitives.

// RTK Query tag-based invalidation example (pseudocode)

Recommendation: Define clear cache rules per resource to avoid stale UI.

29. What is code splitting with Redux and how to do it?

Code splitting loads feature code on demand. Dynamically inject reducers or slices when the feature loads to avoid initial bundle bloat (e.g., use store.injectReducer or configureStore with reducer manager).

store.injectReducer('feature', featureReducer);

Use case: Large apps with infrequently used features.

30. How do you manage optimistic cache invalidation?

Invalidate or update cache entries immediately after optimistic actions, then confirm or rollback based on server response. RTK Query supports optimistic updates via updateQueryData.

dispatch(api.util.updateQueryData('getItems', undefined, draft => { draft.push(newItem); }));

Careful: Keep rollback logic clear to avoid inconsistent state.

31. How to handle WebSocket or real-time events with Redux?

Open WebSocket connection in middleware or a service, dispatch actions on incoming messages to update the store, and keep connection state in Redux to reflect connectivity and reconnection attempts.

ws.onmessage = e => dispatch(receiveMessage(JSON.parse(e.data)) );

Pattern: Use middleware for lifecycle and backpressure handling.

32. What are action type constants vs. slice action creators?

Traditional Redux used string constants for action types. RTK generates action creators and type strings automatically, reducing typos and boilerplate. Prefer RTK slices for new code.

const myAction = slice.actions.myAction; // RTK

Compatibility: You can still interoperate with legacy code using constants.

33. How do you handle cross-tab state synchronization?

Use storage events (localStorage) or BroadcastChannel to sync essential state across tabs. Persist minimal necessary state and reconcile conflicts conservatively.

window.addEventListener('storage', e => { /* sync logic */ });

Note: Avoid syncing large or sensitive state across tabs.

34. How to handle undo/redo functionality in Redux?

Keep a history stack in the store: past, present, future. Dispatch actions to push snapshots and move pointers for undo/redo. Libraries like redux-undo can simplify this.

past.push(present); present = nextState;

Performance: Snapshot size matters; store diffs for large state.

35. How to measure and improve Redux performance?

Profile renders with React DevTools, use memoized selectors, split state to reduce re-renders, minimize connected components, and avoid passing new object references frequently. Use immutable patterns and RTK for efficient updates.

const memoized = createSelector([selectItems], items => items.filter(...));

Tip: Only optimize when bottlenecks are identified.

36. How to implement multi-tenant or multi-namespace state?

Design state with tenant keys or namespaces to isolate data. Use selectors that accept a tenant id and maintain per-tenant caches to avoid cross-tenant leaks.

state.tenants = { tenantId: { data: {...} } };

Security: Enforce tenant isolation on the server too.

37. What strategies exist for migrating from Context to Redux?

Migrate incrementally by identifying shared contexts, introducing slices for those domains, and wrapping components with Provider while keeping existing Context until cutover. Use selectors to map store to prior Context consumers.

// migrate by replacing useContext with useSelector gradually

Approach: Test each step to avoid regressions.

38. How do you handle ephemeral UI state vs persistent state?

Keep ephemeral UI state (modals, input focus) local to components. Persist only what must survive reloads (auth tokens, preferences) and store them securely (localStorage with encryption if needed). Use Redux Persist for selective persistence.

persistConfig = { key: 'root', whitelist: ['auth'] };

Rule: Default to local state unless sharing or persistence is required.

39. How to safely store authentication tokens with Redux?

Avoid storing long-lived sensitive tokens in the Redux store. Store short-lived access tokens in memory or HttpOnly cookies and refresh via secure endpoints. If you must persist tokens, use secure storage and rotate frequently.

// store only token metadata in Redux, not raw secrets

Security: Treat Redux as client-visible state and avoid secrets.

40. How to integrate Redux with TypeScript?

Use typed slices, ActionCreators and RootState types. Leverage RTK’s TypeScript support for strongly-typed actions and selectors to catch errors early and improve DX.

type RootState = ReturnType<typeof store.getState>; const selector = (s: RootState) => s.auth;

Best practice: Keep types colocated with slice files.

41. How to handle server-side rendering (SSR) with Redux?

On the server, create a fresh store per request, preload state with data fetching, render component tree to HTML, then serialize initial state for the client to hydrate a matching store.

const store = configureStore({ reducer: rootReducer }); const html = renderToString(<Provider store={store}>...);

Careful: Avoid leaking request-specific data across requests.

42. How to implement feature flags with Redux?

Store feature flag configuration in a feature slice or use a remote config service. Use selectors to gate UI and actions. Keep the flagging system flexible for runtime toggles.

const isEnabled = useSelector(state => state.flags['newFeature']);

Tip: Use server-controlled flags for AB testing.

43. How to handle localization (i18n) in Redux apps?

Store the selected locale and minimal localization metadata in Redux, while using i18n libraries (react-intl, i18next) for translations. Avoid storing full translation resources in the store.

const locale = useSelector(state => state.settings.locale);

Pattern: Load translation bundles dynamically per locale.

44. How to manage side effects outside Redux (analytics, logging)?

Use middleware to dispatch analytics events on specific actions or integrate external services at the middleware layer. Keep analytics calls idempotent and resilient to failures.

const analyticsMiddleware = store => next => action => { if(action.meta?.analytics) sendEvent(action); return next(action); };

Tip: Avoid coupling business logic with analytics; use metadata tags.

45. How to perform lazy reducer registration safely?

Implement a reducer manager that can add/remove reducers at runtime and reconfigure the store’s root reducer. Ensure you persist relevant state slices or initialize defaults when injecting.

store.injectReducer('foo', fooReducer);

Use case: Micro-frontends and code-splitting.

46. How to design extensible action payloads?

Design payloads as objects with clear fields instead of positional arguments. Include correlation ids and metadata to track async flows and simplify middleware handling.

dispatch({ type: 'SAVE', payload: { id, data }, meta: { correlationId } });

Advantage: Easier debugging and middleware interoperability.

47. How to implement retry logic for failed requests?

Implement retry in middleware or thunks with exponential backoff and max attempts. Track attempts in state or metadata and avoid retrying non-idempotent actions without user confirmation.

for (let i=0;i<max;i++){ try { await api.call(); break; } catch(e) { await wait(backoff(i)); } }

Safety: Respect server rate limits and idempotency.

48. How to document Redux APIs for teams?

Document slice public APIs: exported actions, selectors, and expected payload shapes. Use README per feature, example usage snippets, and TypeScript types to enforce contracts.

// README in /src/features/user/README.md

Benefit: Easier onboarding and fewer misuses of slice internals.

49. How to safely evolve state shape (migrations)?

Version persisted state and write migration functions to transform older versions to the new shape during hydration. Keep migrations small and reversible when possible.

persistTransform = { version: 2, migrate: (state) => migrateState(state) };

Practice: Test migrations with snapshoted persisted states.

50. What are common anti-patterns in Redux and how to avoid them?

Common anti-patterns include storing non-serializable data, overusing global state for local UI, mutating state in reducers, and putting side effects inside reducers. Avoid these by using serializable payloads, local component state where appropriate, immutable patterns (Immer/RTK), and middleware for side effects.

// avoid storing functions or class instances in Redux state

Summary: Follow single-responsibility, keep reducers pure, and use RTK patterns to reduce errors.