Ace Your React Technical Interview Without Memorizing Buzzwords
Most interview prep articles for React developers simply list ten questions and leave you with vague bullet points. When you sit down with a senior engineer or engineering lead, generic definitions will not get you past the screen. Interviewers want to see whether you understand how React actually manages state, re-renders the virtual DOM, executes hook lifecycles, and handles edge cases in production applications.
Here are the top ten React interview questions with thorough explanations, code examples, and common traps you must avoid.
1. What is the difference between props and state, and when do you derive state?
Props are read-only configuration values passed down from a parent component to a child component. A child component must never mutate its own props.
State is internal, mutable memory managed by the component itself across renders. When state changes, React schedules a re-render of that component and its children.
The Trap: Storing calculated values in state. If you can compute a value from existing props or state during render, do not store it in duplicate state. Duplicate state leads to synchronization bugs.
// Bad: Redundant state sync
function ProductList({ items, filter }) {
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(items.filter(i => i.category === filter));
}, [items, filter]);
// ...
}
// Good: Derived state calculated on the fly
function ProductList({ items, filter }) {
const filteredItems = items.filter(i => i.category === filter);
// ...
}
2. How do React Hooks work under the hood, and why do the rules exist?
React does not attach hooks to components using magical key names. Inside React fiber architecture, each component instance maintains a linked list of hook nodes. Every time useState or useEffect runs, React advances an internal pointer to the next node in that linked list.
This is why you must never call hooks inside loops, conditions, or nested functions. If an early return or conditional statement causes a hook to be skipped, the hook pointer gets desynchronized from the previous render, causing state data to map to the wrong hook.
3. How do you prevent race conditions in async useEffect calls?
A classic bug in React applications happens when a user quickly clicks between options (for example, switching from User ID 1 to User ID 2). If the network request for ID 1 finishes after the request for ID 2, the UI will display stale data. To prevent this, use a cleanup flag or the native AbortController API:
import { useState, useEffect } from "react";
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<UserData | null>(null);
useEffect(() => {
const controller = new AbortController();
async function loadData() {
try {
const res = await fetch(`/api/users/${userId}`, {
signal: controller.signal
});
const data = await res.json();
setUser(data);
} catch (err: any) {
if (err.name !== "AbortError") {
console.error("Fetch error:", err);
}
}
}
loadData();
// Cancel in-flight request if userId changes before response arrives
return () => {
controller.abort();
};
}, [userId]);
if (!user) return <p>Loading...</p>;
return <div>{user.name}</div>;
}
4. When should you use useMemo and useCallback, and when are they premature optimization?
Every function and object created inside a functional component gets a new memory reference on every render. However, wrapping every simple calculation in useMemo adds memory overhead and dependency array comparison costs that often outweigh any render savings.
Only reach for memoization in two specific situations:
- You are passing a callback function or object reference into a heavily optimized child component that is wrapped in
React.memo. - The calculation is demonstrably expensive (such as sorting or filtering tens of thousands of items in memory).
5. How does React 18 and 19 handle concurrency and batching?
In older React versions, state updates inside setTimeout, promises, or native event handlers were not batched together. Each state setter triggered an independent DOM update. Modern React implements Automatic Batching everywhere, combining multiple state updates into a single render pass regardless of where they are called.
Furthermore, useTransition allows developers to mark non-urgent UI updates (such as filtering a large table) as interruptible, keeping the main input thread responsive even during heavy rendering workloads.
6. What is the difference between Server Components and Client Components?
In modern React architectures (like Next.js App Router), React Server Components (RSC) execute exclusively on the server. They have zero impact on your client-side JavaScript bundle size, can query databases directly without exposing API keys, and stream rendered HTML directly to the browser.
Client Components (marked with the "use client" directive) are standard interactive components that hydrate in the browser. They manage local state, listen to browser events like onClick, and use client-side hooks like useState and useEffect.
7. How do you handle runtime errors gracefully in React?
Standard JavaScript try/catch blocks do not catch render errors thrown inside child component trees. React requires Error Boundaries to catch render errors, log diagnostic traces, and display fallback interfaces without crashing the entire page.
import React, { Component, ReactNode } from "react";
interface Props {
children: ReactNode;
fallback: ReactNode;
}
interface State {
hasError: boolean;
}
export class SafeErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error("Caught UI error:", error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
8. Context API versus External State Managers (Zustand, Redux Toolkit): When to pick which?
A common interview trap is answering that Context API replaces Redux or Zustand. The React team designed Context for dependency injection and infrequent global updates (such as theme toggles, current locale, or user session state).
Context has a fundamental performance constraint: every component that subscribes to a context will re-render whenever that context value changes, even if the component only cares about one specific property. For high-frequency state updates (complex form wizards, real-time dashboards, shopping carts), dedicated stores like Zustand or Redux Toolkit provide selector-based subscriptions that prevent unnecessary re-renders.
9. How do you approach testing React components effectively?
Avoid testing implementation details (such as inspecting component internal state or checking method calls). Use React Testing Library to test components from the perspective of an actual user.
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Counter } from "./Counter";
test("increments counter when button is clicked", async () => {
const user = userEvent.setup();
render(<Counter />);
const button = screen.getByRole("button", { name: /increment/i });
expect(screen.getByText(/count: 0/i)).toBeInTheDocument();
await user.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
10. What is Virtual DOM Reconciliation, and why are keys important?
When state changes, React builds a new virtual DOM tree and compares it to the previous tree using a heuristic diffing algorithm known as reconciliation. Because comparing two arbitrary trees has an O(n^3) complexity, React relies on two assumptions to achieve O(n) performance:
- Two elements of different types will produce different trees.
- Elements in dynamic lists can be tracked across renders using unique, stable
keyattributes.
If you use array indices as keys and insert or remove items from the middle of the list, React confuses which DOM nodes correspond to which items, causing form inputs and animation states to misbehave.
Official Resources to Stay Current
To continue sharpening your React skills, stick to current documentation and active community specifications:
- React Official Documentation: The interactive guides at react.dev cover modern idioms, Hooks, and Server Components.
- React Testing Library: Best practices for behavioral component testing at testing-library.com.
- TypeScript Handbook: Typed React patterns and component prop interfaces at typescriptlang.org.
When you walk into your next interview, do not recite definitions. Focus on why architectural trade-offs exist, show real code, and explain how you prevent production performance bugs.
