Most React interview questions are trivia dressed up as skill assessment. "What does useRef return?" doesn't tell you if someone can debug a hydration mismatch or design a state architecture that won't become unmaintainable six months later.
These questions do.
Why does my component re-render?
A component re-renders when:
- Its own state changes
- A context value it consumes changes
- Its parent re-renders — unless you've memoized it
Point three trips people up. If you wrap children in React.memo and pass a new function reference on every render, you've gained nothing. The shallow comparison catches the new function reference and re-renders anyway.
The real test: ask a candidate to solve a specific re-render problem. Someone who can draw the re-render tree and trace a state change through it understands React. Someone who memorized the rules is guessing.
When would you skip useMemo?
Everyone knows when to use it. The better question is: when should you not?
Memoization costs CPU. Tracking dependencies, running comparisons — for cheap operations (sorting 10 items, string concatenation), the overhead is worse than just recalculating. If you're memoizing everything, you've created work instead of saved it.
Memoize when the computation is genuinely expensive or when the value is a dependency in another useEffect/useMemo. Everything else: calculate fresh.
useCallback vs useMemo
useCallback(fn, deps) is useMemo(() => fn, deps). Same thing, different data type.
Use useCallback for function references you pass to memoized children. Without it, a new reference on every render defeats React.memo.
// Without useCallback — memoized child re-renders every time
const handleClick = () => setCount(c => c + 1)
<ExpensiveList onClick={handleClick} />
// With useCallback — memoized child skips render
const handleClick = useCallback(() => setCount(c => c + 1), [])
<ExpensiveList onClick={handleClick} />
If you're using useCallback on a component that isn't memoized, you're doing unnecessary work for no benefit. Profile first, memoize second.
Context performance
Context re-renders every consumer when its value changes. Fine for theme or auth. Catastrophic for frequently updated state.
The mistake: putting a useState value directly into context and expecting it to be performant.
// This re-renders every consumer on every keystroke
const UserContext = createContext({ name: '', setName: () => {} })
// Better: split contexts, use a stable reference for dispatch
const CountContext = createContext<() => void>(() => {})
If you need high-frequency context updates (form state, selection state), reach for Zustand instead.
useTransition and useDeferredValue
React 18 APIs that separate urgent updates from deferred ones. Most candidates haven't used them in production. Most real applications need them.
useTransition marks a state update as non-urgent:
const [isPending, startTransition] = useTransition()
const [input, setInput] = useState('')
const [results, setResults] = useState([])
setInput(e.target.value)
startTransition(() => {
setResults(expensiveSearch(e.target.value))
})
If a higher-priority update (like a click) arrives mid-render, React handles it first. The UI stays responsive. Use it for search inputs, filter panels, and any list that shouldn't block user interaction.
Server Components vs Client Components
Next.js-specific but it's the most asked Next.js question in 2025-2026.
Server Components render on the server and send HTML to the client. No JavaScript ships for them. Client Components ('use client') render in the browser and handle interactivity.
Use Server Components by default. Add 'use client' only when you need browser APIs, state, or event handlers. Server Components can import Client Components; Client Components cannot import Server Components. Move the client boundary as low in the tree as possible.
The interview trap: candidates who say "Server Components can't use hooks." That's true. The better answer explains why that boundary exists and how to design around it.
The rules of hooks (and why they exist)
Hooks must only be called at the top level. Not in conditions, loops, or nested functions. Everyone knows this rule. What they don't know is why.
React tracks hook state by call order. If the order changes between renders, state from hook #3 gets assigned to hook #2. The rules aren't arbitrary — break them and you get subtle, unreproducible bugs that only appear in specific render paths.
Error boundaries
Error boundaries catch JavaScript errors anywhere in the component tree and display a fallback instead of crashing the whole app.
class ErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError() { return { hasError: true } }
render() {
if (this.state.hasError) return <FallbackUI />
return this.props.children
}
}
What trips people up: error boundaries don't catch event handlers, async code outside of render, SSR errors, or errors in the boundary itself.
Hydration mismatch
Hydration is when React attaches event listeners to server-rendered HTML. If the server HTML doesn't match what React generates on the client, you get a hydration error.
Common causes: Date.now() or Math.random() in render, conditional class names based on browser APIs, invalid HTML nesting. Move the non-deterministic code to useEffect (client-only) or add suppressHydrationWarning.
What separates senior candidates
Ask a mid-level dev to debug a slow render. The answer is usually "use the Profiler." The senior answer walks through the tree: where is state lifted, what's being passed as props, are callbacks wrapped in useCallback, is useMemo doing work worth memoizing?
Ask about testing. The wrong answer tests that a hook was called. The right answer tests that a component renders correctly, that a button click triggers the right state change, that an error boundary catches the error. Behavior. Not implementation.
// Good: tests behavior
expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled()
fireEvent.click(screen.getByRole('button'))
expect(screen.getByText(/success/i)).toBeInTheDocument()
// Bad: tests implementation
expect(handleSubmit).toHaveBeenCalledWith({ email: 'test@x.com' })
The candidate who knows userEvent from @testing-library over fireEvent has actually read the docs. That difference matters in real test suites.
What top companies actually pay React engineers
| Level | india |
|---|---|
| Junior (0-2y) | ₹10L – ₹18L |
| Mid (2-5y) | ₹18L – ₹27L |
| Senior (5-8y) | ₹27L – ₹45L |
| Staff (8y+) | ₹45L – ₹65L |
| Level | india |
|---|---|
| Junior (0-2y) | ₹15L – ₹25L |
| Mid (2-5y) | ₹25L – ₹45L |
| Senior (5-8y) | ₹45L – ₹65L |
| Staff (8y+) | ₹65L – ₹90L |
The interview at the company matters more than the question bank. A senior React engineer at Dream11 is doing system design and performance work. They're not being asked to describe the virtual DOM. Target the tier before you start prepping trivia.
The meta point
React interviews at top companies don't test whether you know React. They test whether you can trace state through a component tree, identify performance bottlenecks, design component APIs that don't leak, and make sound architectural decisions under constraints.
If you understand the model, not the syntax, you can look up the API during the interview. Nobody tests whether you remember the exact useMemo signature.
