Interviews

🎯 Interview Guides 12 guides · updated 2026

Real questions and structured answers for data, cloud, and AI engineering interviews β€” including the system-design and GenAI rounds now showing up everywhere.

React Interview Questions and Answers

These questions cover what’s actually tested in frontend and full-stack developer interviews β€” from core React fundamentals to React 19’s concurrent features and modern patterns.


Core Concepts

Q1. What is React and what problem does it solve?

React is a JavaScript library by Meta for building component-based user interfaces. It solves the problem of keeping the UI in sync with state changes efficiently.

Core ideas:

React 18/19 extends this with concurrent rendering, transitions, and Server Components.


Q2. What is the difference between props and state?

// Props β€” data passed from parent; read-only in the receiving component
function Greeting({ name, age }) {
return <h1>Hello, {name} ({age})</h1>;
}
// State β€” private data managed within the component; triggers re-render when changed
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}

Key rules:


Q3. What are React hooks and what are the rules of hooks?

Hooks are functions that let function components use React features (state, lifecycle, context):

Rules of hooks:

  1. Only call hooks at the top level β€” not inside conditionals, loops, or nested functions
  2. Only call hooks from React function components or custom hooks (not regular JS functions)
// CORRECT
function SearchComponent() {
const [query, setQuery] = useState('');
const results = useSearch(query); // Custom hook
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
// WRONG β€” hook inside a condition (breaks rules)
function BrokenComponent({ show }) {
if (show) {
const [count, setCount] = useState(0); // Error!
}
}

Common built-in hooks: useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, useId, useTransition, useDeferredValue.


Q4. Explain useEffect and the cleanup function.

import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false; // Guard against race conditions
async function loadUser() {
const data = await fetchUser(userId);
if (!cancelled) setUser(data);
}
loadUser();
// Cleanup function β€” runs before next effect OR on unmount
return () => {
cancelled = true;
};
}, [userId]); // Dependency array β€” re-runs when userId changes
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
// Dependency array behavior:
// [] (empty) β€” runs once on mount, cleanup on unmount
// [dep1, dep2] β€” runs when deps change
// omitted β€” runs on every render (usually wrong)

State Management

Q5. What is the difference between useState and useReducer?

// useState β€” best for simple, independent state values
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// useReducer β€” best for complex state with multiple related actions
import { useReducer } from 'react';
const initialState = { count: 0, status: 'idle', error: null };
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'FETCH_START':
return { ...state, status: 'loading', error: null };
case 'FETCH_SUCCESS':
return { ...state, status: 'success', count: action.payload };
case 'FETCH_ERROR':
return { ...state, status: 'error', error: action.payload };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>Count: {state.count} ({state.status})</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</>
);
}

Use useReducer when state transitions are complex, when the next state depends on multiple previous values, or when you want co-located, testable action logic.


Q6. What is the Context API and when would you use Redux instead?

import { createContext, useContext, useState } from 'react';
// Create context
const ThemeContext = createContext('light');
// Provider wraps the component tree
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<MainLayout />
</ThemeContext.Provider>
);
}
// Any descendant can consume without prop drilling
function ThemeToggle() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Current: {theme}
</button>
);
}

Context works well for: theme, locale, auth user, feature flags β€” data that’s global but changes infrequently.

Redux / Zustand / Jotai when you need: complex state with many updates, time-travel debugging, middleware (logging, analytics), or multiple components subscribing to different slices that update very frequently.


Performance

Q7. What are useMemo and useCallback and when do you actually need them?

import { useMemo, useCallback, memo } from 'react';
// useMemo β€” memoize expensive computed value
function ProductList({ products, searchQuery }) {
// Only recomputes when products or searchQuery changes
const filteredProducts = useMemo(
() => products.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase())),
[products, searchQuery]
);
return filteredProducts.map(p => <ProductCard key={p.id} product={p} />);
}
// useCallback β€” memoize a function reference
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback, new function reference every render β†’
// Child re-renders even when not needed (if it's wrapped in memo)
const handleSubmit = useCallback((data) => {
submitForm(data, count);
}, [count]); // Only new function when count changes
return <ExpensiveChild onSubmit={handleSubmit} />;
}
// memo β€” prevent re-render if props haven't changed (shallowly)
const ExpensiveChild = memo(function ExpensiveChild({ onSubmit }) {
return <button onClick={() => onSubmit({})}>Submit</button>;
});

Important: these optimizations have a cost (memory, complexity). Only add them when you have a measured performance problem.


Q8. What is React’s reconciliation algorithm and what is the key prop for?

Reconciliation is how React determines the minimal set of DOM changes needed when state updates. React diffs the previous and next virtual DOM trees:

The key prop lets React identify which list items have changed, been added, or removed:

// BAD β€” key based on index; if items reorder, React sees same positions β†’ wrong updates
{items.map((item, index) => (
<TodoItem key={index} todo={item} />
))}
// GOOD β€” stable, unique key
{items.map(item => (
<TodoItem key={item.id} todo={item} />
))}
// Also: avoid generating keys randomly (Math.random()) β€” defeats the purpose

Keys must be unique among siblings, not globally unique. Keys help React match items across renders, preserving component state for stable items.


Q9. What is lazy loading and Suspense in React?

import { lazy, Suspense } from 'react';
// Code-split a component β€” bundle for HeavyChart is loaded only when rendered
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div className="skeleton">Loading chart...</div>}>
<HeavyChart data={data} />
</Suspense>
</div>
);
}
// Nested Suspense boundaries for granular loading states
function App() {
return (
<Suspense fallback={<AppSkeleton />}>
<Header />
<Suspense fallback={<ChartSkeleton />}>
<Dashboard />
</Suspense>
</Suspense>
);
}

Suspense also works with data fetching when using React 19’s use() hook or frameworks like Next.js that integrate with React’s concurrent features.


Modern React

Q10. What is useTransition and when do you use it?

useTransition marks a state update as non-urgent, letting React keep the UI responsive while the update is being processed:

import { useState, useTransition } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
function handleChange(e) {
setQuery(e.target.value); // Urgent β€” update input immediately
startTransition(() => { // Non-urgent β€” can be interrupted
const filtered = heavySearch(e.target.value);
setResults(filtered);
});
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <p>Searching...</p>}
<ResultsList results={results} />
</div>
);
}

The input stays responsive while the expensive results re-render happens concurrently. If the user types again before results finish, React discards the old transition and starts a new one.


Q11. What are React Server Components (RSC) and how do they differ from Client Components?

React Server Components render on the server and send only HTML β€” no JavaScript shipped for them to the client:

// page.tsx (Server Component β€” runs on server, no 'use client')
async function UserProfile({ userId }) {
// Direct DB access β€” no API layer needed!
const user = await db.users.findById(userId);
const posts = await db.posts.findByUserId(userId);
return (
<div>
<h1>{user.name}</h1>
{/* InteractiveButton is a Client Component */}
<InteractiveButton userId={userId} />
<PostList posts={posts} />
</div>
);
}
InteractiveButton.tsx
'use client'; // This directive makes it a Client Component
import { useState } from 'react';
function InteractiveButton({ userId }) {
const [followed, setFollowed] = useState(false);
return (
<button onClick={() => setFollowed(f => !f)}>
{followed ? 'Following' : 'Follow'}
</button>
);
}
Server ComponentsClient Components
JavaScript sent to clientNoYes
Can use hooksNoYes
Can fetch data directlyYes (async/await)No (useEffect or SWR)
Access to browser APIsNoYes
Default in Next.js App RouterYesMust add 'use client'

Q12. What’s new in React 19 (2024)?

Actions β€” built-in way to handle async form submissions without extra state:

function ContactForm() {
async function submitAction(formData) {
'use server'; // Server Action
await sendEmail(formData.get('email'));
}
return (
<form action={submitAction}>
<input name="email" type="email" />
<button type="submit">Send</button>
</form>
);
}

use() hook β€” read resources (promises, context) inline in render:

const data = use(fetchPromise); // Suspends until resolved
const theme = use(ThemeContext); // Like useContext but composable

useFormStatus β€” track pending state of an enclosing form:

function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Sending...' : 'Send'}</button>;
}

useOptimistic β€” show optimistic UI while async action is pending:

const [optimisticMessages, addOptimistic] = useOptimistic(messages);

Ref as a prop β€” no longer need forwardRef; just pass ref as a regular prop.


Custom Hooks & Patterns

Q13. Write a custom hook for debouncing a value.

import { useState, useEffect } from 'react';
function useDebounce<T>(value: T, delay: number = 300): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer); // Clear timer if value changes before delay
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchBox() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
searchAPI(debouncedQuery); // Only fires 300ms after user stops typing
}
}, [debouncedQuery]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

Q14. What is the compound component pattern?

Compound components share implicit state through context without explicit prop passing:

import { createContext, useContext, useState } from 'react';
const AccordionContext = createContext(null);
function Accordion({ children, defaultOpen = null }) {
const [openId, setOpenId] = useState(defaultOpen);
return (
<AccordionContext.Provider value={{ openId, setOpenId }}>
<div className="accordion">{children}</div>
</AccordionContext.Provider>
);
}
function AccordionItem({ id, title, children }) {
const { openId, setOpenId } = useContext(AccordionContext);
const isOpen = openId === id;
return (
<div>
<button onClick={() => setOpenId(isOpen ? null : id)}>
{title}
</button>
{isOpen && <div className="content">{children}</div>}
</div>
);
}
// Clean, expressive usage
<Accordion defaultOpen="faq-1">
<AccordionItem id="faq-1" title="What is React?">
React is a UI library...
</AccordionItem>
<AccordionItem id="faq-2" title="What are hooks?">
Hooks are functions...
</AccordionItem>
</Accordion>