← Back to DevBytes

Top 50 React Interview Questions for Mid-Level Developers

Top 50 React Interview Questions for Mid-Level Developers

React remains one of the most in-demand frontend libraries, and mid-level developers are expected to demonstrate a solid grasp of its internals, patterns, performance characteristics, and ecosystem. This tutorial walks through the 50 most commonly asked React interview questions, grouped by theme, with practical code examples and best practices. Whether you're preparing for an interview or refreshing your knowledge, this guide will help you articulate your understanding confidently.

Why This Matters

Mid-level React developers are judged not just on whether they can build features, but on whether they understand why things work the way they do. Interviewers want to see that you can reason about re-renders, choose appropriate patterns, avoid common pitfalls, and write maintainable code. The questions below reflect real-world concerns that come up in production codebases every day.

Section 1: Core React Concepts (Questions 1–10)

1. What is the Virtual DOM and how does React use it?

The Virtual DOM is an in-memory representation of the real DOM. React keeps a lightweight copy of the UI tree and, when state changes, computes a diff between the previous and next versions. It then applies only the necessary updates to the real DOM, minimizing expensive browser reflows.

// Conceptually, React does something like:
const prevTree = <div><span>Hello</span></div>;
const nextTree = <div><span>World</span></div>;
// Diff finds only the text node changed, updates just that.

2. What is JSX and how does it differ from HTML?

JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. It compiles to React.createElement() calls. Key differences from HTML include using className instead of class, htmlFor instead of for, camelCase event handlers (onClick), and the ability to embed JavaScript expressions with curly braces.

const Button = ({ label, onClick }) => (
  <button className="btn" onClick={onClick}>
    {label.toUpperCase()}
  </button>
);

3. What is the difference between elements and components?

An element is a plain object describing what should appear on screen β€” it is immutable. A component is a function or class that returns elements. Elements are the inputs to components.

const element = <h1>Hi</h1>; // React element

const Greeting = ({ name }) => <h1>Hi {name}</h1>; // Component
const element2 = <Greeting name="Sam" />; // Element from component

4. What are controlled vs uncontrolled components?

A controlled component has its value driven by React state, while an uncontrolled component manages its own state via the DOM, typically accessed through refs.

// Controlled
function Controlled() {
  const [value, setValue] = useState('');
  return <input value={value} onChange={e => setValue(e.target.value)} />;
}

// Uncontrolled
function Uncontrolled() {
  const inputRef = useRef();
  return <input ref={inputRef} defaultValue="hello" />;
}

5. What is the difference between state and props?

Props are read-only inputs passed from a parent to a child. State is internal, mutable data owned by the component. Changing state triggers a re-render; mutating props is not allowed.

6. What are keys and why are they important?

Keys help React identify which items in a list have changed, been added, or removed. They should be stable and unique. Using array indices as keys can cause subtle bugs when list items reorder.

{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

7. What is reconciliation?

Reconciliation is the algorithm React uses to diff one tree with another to determine which parts need updating. It uses heuristics: different component types produce different trees, and keys identify children across renders.

8. What is the difference between rendering and committing?

React's update flow has two phases: render (computing what changed β€” pure, can be interrupted) and commit (applying changes to the DOM β€” synchronous). Understanding this helps explain why side effects belong in useEffect, not in the render body.

9. What are fragments and why use them?

Fragments let you group multiple elements without adding extra DOM nodes.

function List() {
  return (
    <>
      <dt>Term</dt>
      <dd>Definition</dd>
    </>
  );
}

10. What is the difference between createElement and JSX?

JSX is syntactic sugar over React.createElement(type, props, ...children). They produce identical output. JSX is preferred for readability.

Section 2: Hooks (Questions 11–20)

11. What are the Rules of Hooks?

These rules ensure hooks are called in the same order on every render, which is how React associates state with each hook.

12. How does useState work under the hood?

React maintains an internal array of state slots per component. Each useState call reads or writes the next slot. This is why order matters. The setter function schedules a re-render with the new value.

const [count, setCount] = useState(0);
// Functional update avoids stale closures:
setCount(prev => prev + 1);

13. What is the difference between useEffect and useLayoutEffect?

useEffect runs asynchronously after the browser paints. useLayoutEffect runs synchronously after DOM mutations but before paint β€” useful for measuring layout and avoiding visual flicker.

14. How do you clean up effects?

Return a cleanup function from the effect. React calls it before the next effect runs and on unmount.

useEffect(() => {
  const id = setInterval(() => tick(), 1000);
  return () => clearInterval(id);
}, []);

15. What does useMemo do and when should you use it?

useMemo memoizes a computed value, recomputing only when dependencies change. Use it for expensive calculations or to preserve referential equality of objects passed to memoized children.

const sorted = useMemo(() => expensiveSort(data), [data]);

16. What does useCallback do?

useCallback memoizes a function reference. It is useful when passing callbacks to optimized child components that rely on reference equality to skip re-renders.

const handleClick = useCallback(() => {
  setCount(c => c + 1);
}, []);

17. What is useRef used for?

useRef holds a mutable value that persists across renders without triggering re-renders. Common uses: accessing DOM nodes and storing mutable values like timers.

const inputRef = useRef(null);
useEffect(() => inputRef.current?.focus(), []);

18. What is useReducer and when would you choose it over useState?

useReducer manages state via a reducer function and actions. Prefer it when state transitions are complex, interdependent, or when the next state depends on multiple values.

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    default: return state;
  }
}
const [state, dispatch] = useReducer(reducer, { count: 0 });

19. What is useContext and how do you avoid re-render pitfalls?

useContext subscribes a component to a context value. Any consumer re-renders when the provider's value changes. To avoid excessive re-renders, split contexts by concern or memoize the value.

const ThemeContext = createContext('light');
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click</button>;
}

20. How do you write a custom hook?

A custom hook is a function prefixed with use that may call other hooks. It encapsulates reusable stateful logic.

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return width;
}

Section 3: Component Patterns & Architecture (Questions 21–30)

21. What is the difference between presentational and container components?

Presentational components focus on UI and receive data via props. Container components handle data fetching and state, passing props down. This separation improves reusability and testability, though modern hooks blur the line.

22. What is the render props pattern?

A component accepts a function as a prop that returns React elements, enabling logic sharing without inheritance.

const MouseTracker = ({ render }) => {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  return <div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>{render(pos)}</div>;
};
// Usage: <MouseTracker render={pos => <p>{pos.x}, {pos.y}</p>} />

23. What are higher-order components (HOCs)?

An HOC is a function that takes a component and returns a new component with added behavior. They are largely replaced by hooks but still appear in legacy code.

function withLoading(Component) {
  return function Wrapped({ isLoading, ...props }) {
    return isLoading ? <p>Loading…</p> : <Component {...props} />;
  };
}

24. What is prop drilling and how do you avoid it?

Prop drilling is passing props through many intermediate components. Solutions include Context, composition, or state management libraries like Redux or Zustand.

25. What is the compound component pattern?

Compound components let related components work together implicitly through shared context, providing a clean API.

const Select = ({ children, value, onChange }) => (
  <SelectContext.Provider value={{ value, onChange }}>
    {children}
  </SelectContext.Provider>
);
Select.Option = ({ value, children }) => { /* reads context */ };
// Usage:
<Select value={v} onChange={setV}>
  <Select.Option value="a">A</Select.Option>
</Select>

26. What is lazy loading and how do you implement it?

Lazy loading defers loading a component until it is needed, reducing initial bundle size.

const Settings = React.lazy(() => import('./Settings'));
function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Settings />
    </Suspense>
  );
}

27. What is an error boundary?

An error boundary is a class component that catches errors in its child tree during rendering, lifecycle, and constructors, displaying a fallback UI instead of crashing.

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError() { return { hasError: true }; }
  componentDidCatch(error, info) { logError(error, info); }
  render() {
    return this.state.hasError ? <h1>Something went wrong.</h1> : this.props.children;
  }
}

28. What is the difference between default and named exports?

A module can have one default export and many named exports. Default imports can be renamed freely; named imports must match. Consistency within a codebase matters more than the choice itself.

29. How do you forward refs to child components?

Use React.forwardRef to pass a ref through a component to a DOM node.

const FancyInput = React.forwardRef((props, ref) => (
  <input ref={ref} className="fancy" {...props} />
));

30. What is the difference between controlled forms and form libraries?

Controlled forms give full control but can become verbose. Libraries like React Hook Form reduce re-renders and boilerplate by using uncontrolled inputs with validation built in.

Section 4: State Management (Questions 31–40)

31. When should you use global state vs local state?

Use local state for UI concerns scoped to one component. Use global state when many unrelated components need the same data, or when state must persist across route changes.

32. What is Redux and what problem does it solve?

Redux is a predictable state container with a single store, actions, and reducers. It centralizes state, enables time-travel debugging, and enforces unidirectional data flow. It is most valuable in large apps with complex state interactions.

33. What is Redux Toolkit and why use it?

Redux Toolkit (RTK) is the official, opinionated way to write Redux. It reduces boilerplate with createSlice, includes Immer for immutable updates, and configures sensible defaults.

const counter = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => { state.value += 1; },
  },
});
export const { increment } = counter.actions;
export default counter.reducer;

34. What are alternatives to Redux?

Common alternatives include Zustand (minimal), Jotai (atomic), Recoil (atomic), MobX (observable), and React Context. The right choice depends on app size, team familiarity, and performance needs.

35. How does Context differ from Redux?

Context provides a way to pass values through the tree without prop drilling, but it is not a state manager β€” it has no built-in optimization for selective subscriptions. Redux offers middleware, devtools, and fine-grained subscriptions.

36. What is the Flux pattern?

Flux is a unidirectional data flow pattern: actions dispatch to a store, the store updates, and views re-render. Redux is an implementation of Flux ideas.

37. How do you persist state across page reloads?

Use localStorage, sessionStorage, or IndexedDB. Libraries like redux-persist automate this for Redux stores.

const [user, setUser] = useState(() =>
  JSON.parse(localStorage.getItem('user') || 'null')
);
useEffect(() => localStorage.setItem('user', JSON.stringify(user)), [user]);

38. What is server state and why is it different from client state?

Server state is data fetched from an API β€” it is asynchronous, can be stale, and is owned by the server. Libraries like React Query and SWR manage caching, invalidation, and refetching, separating server state from UI state.

39. What is optimistic UI updates?

Optimistic updates immediately reflect a change in the UI before the server confirms it, then roll back on failure. This improves perceived performance.

const updateTodo = async (todo) => {
  queryClient.setQueryData(['todos'], old => applyChange(old, todo));
  try {
    await api.update(todo);
  } catch {
    queryClient.invalidateQueries(['todos']); // rollback
  }
};

40. How do you handle asynchronous logic in Redux?

Use middleware like Redux Thunk for simple async, or Redux Toolkit's createAsyncThunk for action lifecycle management (pending, fulfilled, rejected).

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

Section 5: Performance & Optimization (Questions 41–50)

41. What causes unnecessary re-renders and how do you prevent them?

Re-renders occur when state or context changes, or when a parent re-renders. Prevent them with React.memo, useMemo, useCallback, proper dependency arrays, and splitting context.

42. What does React.memo do?

React.memo memoizes a component, re-rendering only when props change by shallow comparison. It helps when a component renders often with the same props.

const ExpensiveList = React.memo(function List({ items }) {
  return items.map(i => <li key={i.id}>{i.name}</li>);
});

43. What is code splitting and why is it important?

Code splitting breaks your bundle into smaller chunks loaded on demand, improving initial load time. Use dynamic import(), route-based splitting, or tools like react-loadable.

44. What is the difference between useMemo and useCallback?

useMemo memoizes a value; useCallback memoizes a function. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

45. How do you optimize large lists in React?

Use virtualization libraries like react-window or react-virtualized to render only visible rows, dramatically reducing DOM nodes.

import { FixedSizeList } from 'react-window';
const Row = ({ index, style }) => <div style={style}>{items[index].name}</div>;
<FixedSizeList height={400} itemCount={items.length} itemSize={35}>
  {Row}
</FixedSizeList>

46. What is debouncing and throttling in React?

Debouncing delays execution until a pause in events (good for search inputs). Throttling limits execution rate (good for scroll/resize). Both reduce unnecessary work.

function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

47. What is the React Profiler and how do you use it?

The React DevTools Profiler records renders and shows component render times and reasons. Use it to identify bottlenecks before optimizing. The <Profiler> component also enables programmatic measurement.

<Profiler id="App" onRender={(id, phase, actualDuration) => {
  console.log(id, phase, actualDuration);
}}>
  <App />
</Profiler>

48. What are the best practices for dependency arrays?

49. How does React 18's concurrent rendering change performance?

Concurrent rendering lets React prepare multiple versions of the UI without blocking the main thread. Features like useTransition and useDeferredValue keep the UI responsive during expensive updates.

const [isPending, startTransition] = useTransition();
const onSearch = (q) => {
  startTransition(() => setResults(filter(q)));
};

50. What are common performance anti-patterns in React?

Best Practices Summary

Conclusion

Mastering these 50 questions gives you a strong foundation for any mid-level React interview. The key is not memorizing answers but understanding the underlying principles β€” why re-renders happen, how hooks track state, when to reach for global state, and how to measure performance before optimizing. Pair this knowledge with hands-on experience building and debugging real applications, and you will be well prepared to discuss React thoughtfully and demonstrate the depth that interviewers expect from mid-level engineers.

β€” Ad β€”

Google AdSense will appear here after approval

← Back to all articles