Skip to main content
    Interview Questions

    React Developer Interview 2026: 50+ Hooks, Performance & State Management Questions

    React interviews in 2026 separate developers who build working components from engineers who understand how React actually works. Why does useEffect run twice in development? How does React's fiber architecture schedule work? When should you memoize? These 50+ questions reveal what top companies ...

    January 4, 2026
    40 min read
    18 views
    Craqly Team
    React Developer Interview 2026: 50+ Hooks, Performance & State Management Questions
    react interview 2026
    react hooks questions
    react performance interview
    state management interview
    react render optimization
    useeffect dependency arrays
    react fiber
    react server components
    interview
    interview questions

    Domain Overview

    React has matured from a view library into an ecosystem. In 2026, React developers need to understand not just components and props, but Server Components, Suspense, concurrent rendering, and the nuances of hooks. The bar has risen considerably.

    What makes React interviews challenging is that the syntax is simple, but the concepts are deep. Anyone can write a component that renders data—the questions that matter are about performance, state management at scale, and understanding React's rendering behavior.

    The best React developers I've hired don't just know the API—they understand why React works the way it does. They can explain the reconciliation algorithm, know when to reach for useCallback vs useMemo, and can debug a re-render cascade without console.logging everywhere.

    Key Skills Interviewers Look For

    • Hooks Mastery: useState, useEffect, useCallback, useMemo, useRef, custom hooks
    • State Management: Context, Redux/Zustand, when to use what
    • Performance: Identifying re-renders, memoization, code splitting
    • Patterns: Compound components, render props, HOCs, controlled vs uncontrolled
    • Modern React: Server Components, Suspense, concurrent features
    • TypeScript: Proper typing for components, hooks, events
    • Testing: React Testing Library, mocking, integration tests
    • Ecosystem: Next.js, routing, data fetching patterns

    Fundamental Questions (Q1-Q15)

    1. Explain the Virtual DOM. Why doesn't React update the real DOM directly?

    Expert Answer:

    The Virtual DOM is a JavaScript representation of the real DOM. React uses it as a layer of abstraction between your code and actual DOM manipulation.

    Why not update the DOM directly?

    • Batching: Multiple state updates can be batched into a single DOM update
    • Diffing: React calculates the minimal changes needed (reconciliation)
    • Predictability: Declarative code is easier to reason about than imperative DOM manipulation
    {`// Without Virtual DOM (imperative)
    element.innerHTML = 'new content';
    element.classList.add('active');
    element.setAttribute('data-id', '123');
    // Each line triggers layout/paint
    
    // With Virtual DOM (declarative)
    setState({ content: 'new content', active: true, id: 123 });
    // React batches and applies minimal changes`}

    Important nuance: The Virtual DOM isn't inherently faster than hand-optimized DOM manipulation. Its value is making updates predictable while being "fast enough."

    2. What are the rules of hooks? Why do they exist?

    Expert Answer:

    The Rules:

    1. Only call hooks at the top level (not in loops, conditions, or nested functions)
    2. Only call hooks from React functions (components or custom hooks)

    Why these rules exist:

    React relies on the order of hook calls to associate state with components. Hooks don't have names—React tracks them by position.

    {`// BAD: Hook order changes between renders
    function Component({ showName }) {
      if (showName) {
        const [name, setName] = useState('');  // Called conditionally!
      }
      const [age, setAge] = useState(0);
    }
    
    // First render: [name, age]
    // Second render (showName=false): [age]
    // React thinks 'age' state is 'name' state!
    
    // GOOD: Consistent hook order
    function Component({ showName }) {
      const [name, setName] = useState('');
      const [age, setAge] = useState(0);
      // Conditionally USE the value, not the hook
    }`}

    3. Explain the difference between useState and useReducer. When would you use each?

    Expert Answer:

    useState: Simple state, independent values

    useReducer: Complex state logic, related state transitions

    {`// useState: Good for simple, independent state
    const [count, setCount] = useState(0);
    const [name, setName] = useState('');
    
    // useReducer: Better for complex, related state
    const initialState = { count: 0, step: 1, history: [] };
    
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return {
            ...state,
            count: state.count + state.step,
            history: [...state.history, state.count]
          };
        case 'setStep':
          return { ...state, step: action.payload };
        case 'reset':
          return initialState;
      }
    }
    
    const [state, dispatch] = useReducer(reducer, initialState);`}

    Use useReducer when:

    • Next state depends on previous state in complex ways
    • Multiple values need to update together
    • State logic is complex enough to extract and test
    • You want to pass dispatch down instead of multiple setters

    4. What's the difference between useCallback and useMemo?

    Expert Answer:

    useMemo: Memoizes a computed value

    useCallback: Memoizes a function reference

    {`// useMemo: Cache expensive computation
    const sortedList = useMemo(() => {
      return items.sort((a, b) => a.price - b.price);
    }, [items]);
    
    // useCallback: Cache function reference
    const handleClick = useCallback(() => {
      console.log(id);
    }, [id]);
    
    // useCallback is actually useMemo for functions:
    // useCallback(fn, deps) === useMemo(() => fn, deps)`}

    When to use them:

    • useMemo: Expensive calculations, derived state
    • useCallback: Functions passed to memoized children (React.memo)

    Warning: Don't overuse them! Memoization has overhead. Only use when you've identified a performance problem or when passing to React.memo components.

    5. Explain the useEffect dependency array. Why does my effect run twice in development?

    Expert Answer:

    Dependency array controls when the effect runs:

    {`useEffect(() => { ... });        // Runs after every render
    useEffect(() => { ... }, []);    // Runs once on mount
    useEffect(() => { ... }, [a, b]); // Runs when a or b changes`}

    Why it runs twice in development (React 18+ Strict Mode):

    React intentionally mounts, unmounts, and remounts components to help you find bugs in your effects. This catches:

    • Missing cleanup functions (subscriptions, timers)
    • Effects that aren't idempotent
    • Race conditions in data fetching
    {`// BAD: No cleanup, will leak subscriptions
    useEffect(() => {
      const sub = api.subscribe(data);
    }, []);
    
    // GOOD: Proper cleanup
    useEffect(() => {
      const sub = api.subscribe(data);
      return () => sub.unsubscribe(); // Cleanup!
    }, []);`}

    6. What is React.memo and when should you use it?

    Expert Answer:

    React.memo is a higher-order component that memoizes the rendered output. It performs a shallow comparison of props and skips re-rendering if props haven't changed.

    {`const ExpensiveList = React.memo(function ExpensiveList({ items }) {
      return items.map(item => );
    });
    
    // With custom comparison
    const MyComponent = React.memo(Component, (prevProps, nextProps) => {
      return prevProps.id === nextProps.id; // Return true to skip render
    });`}

    When to use:

    • Pure functional components with expensive renders
    • Components that re-render often with same props
    • List items in large lists

    When NOT to use:

    • Components that almost always receive new props
    • Simple components (memo overhead isn't worth it)
    • When props include callbacks created inline (they'll always be new!)

    7. Explain controlled vs uncontrolled components.

    Expert Answer:

    {`// Controlled: React state is source of truth
    function Controlled() {
      const [value, setValue] = useState('');
      return (
         setValue(e.target.value)}
        />
      );
    }
    
    // Uncontrolled: DOM is source of truth
    function Uncontrolled() {
      const inputRef = useRef();
      const handleSubmit = () => {
        console.log(inputRef.current.value);
      };
      return ;
    }`}

    When to use controlled: Form validation, conditional disabling, formatting input, most cases

    When to use uncontrolled: File inputs (must be uncontrolled), simple forms, integrating with non-React code

    8-15. More Fundamental Questions:

    • 8. What are keys in React and why are they important?
    • 9. Explain the difference between props and state.
    • 10. What is prop drilling and how do you avoid it?
    • 11. How does React Context work? When should you use it?
    • 12. What are React fragments and when do you use them?
    • 13. Explain the component lifecycle in functional components with hooks.
    • 14. What is the difference between useRef and useState?
    • 15. How do you handle forms in React?

    Intermediate Questions (Q16-Q35)

    16. How do you optimize React performance? Walk through your approach.

    Expert Answer:

    1. Measure first (React DevTools Profiler):

    • Identify components that re-render frequently
    • Find expensive renders (flame chart)
    • Look for unnecessary re-renders

    2. Common optimizations:

    • Memoization: React.memo, useMemo, useCallback (when needed)
    • State colocation: Keep state close to where it's used
    • Virtualization: react-window for long lists
    • Code splitting: React.lazy + Suspense
    • Avoid inline objects/functions: In props passed to memoized children
    {`// Code splitting with lazy loading
    const HeavyComponent = lazy(() => import('./HeavyComponent'));
    
    function App() {
      return (
        }>
          
        
      );
    }`}

    17. Explain React's reconciliation algorithm.

    Expert Answer:

    Reconciliation is how React decides what to update in the DOM. It compares the new Virtual DOM tree with the previous one.

    Key assumptions (make it O(n)):

    1. Elements of different types produce different trees (full rebuild)
    2. Keys hint which children are stable across renders

    How it works:

    • Same element type → Update attributes, recurse on children
    • Different element type → Unmount old tree, mount new tree
    • Lists without keys → Positional comparison (slow, buggy)
    • Lists with keys → Match by key, reorder efficiently

    Why index as key is bad: When items reorder, index doesn't identify the item—it identifies the position. React can't tell if items moved or changed.

    18. How do you test React components?

    Expert Answer:

    {`import { render, screen, fireEvent, waitFor } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    
    // Test rendering and user interaction
    test('submits form with user data', async () => {
      const onSubmit = jest.fn();
      render();
    
      // Query elements like a user would
      await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
      await userEvent.type(screen.getByLabelText(/password/i), 'password123');
      await userEvent.click(screen.getByRole('button', { name: /submit/i }));
    
      expect(onSubmit).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123'
      });
    });
    
    // Test async behavior
    test('loads and displays data', async () => {
      render();
    
      expect(screen.getByText(/loading/i)).toBeInTheDocument();
    
      await waitFor(() => {
        expect(screen.getByText('John Doe')).toBeInTheDocument();
      });
    });`}

    Testing philosophy:

    • Test behavior, not implementation
    • Query elements the way users find them (role, label, text)
    • Avoid testing internal state directly

    19. What are custom hooks? When should you create one?

    Expert Answer:

    Custom hooks are functions that use other hooks to encapsulate reusable logic. They must start with "use".

    {`// Custom hook for API calls
    function useApi(url) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        let cancelled = false;
    
        async function fetchData() {
          try {
            const response = await fetch(url);
            const json = await response.json();
            if (!cancelled) {
              setData(json);
              setLoading(false);
            }
          } catch (e) {
            if (!cancelled) {
              setError(e);
              setLoading(false);
            }
          }
        }
    
        fetchData();
        return () => { cancelled = true; };
      }, [url]);
    
      return { data, loading, error };
    }
    
    // Usage
    function UserProfile({ userId }) {
      const { data: user, loading, error } = useApi(\`/api/users/\${userId}\`);
      // ...
    }`}

    Create custom hooks when:

    • Logic is reused across multiple components
    • Component logic is complex and can be extracted
    • You want to share stateful logic (not UI)

    20-35. More Intermediate Questions:

    • 20. Explain error boundaries. How do you handle errors in React?
    • 21. What is the difference between client-side and server-side rendering in React?
    • 22. How does React Suspense work?
    • 23. Explain the compound component pattern.
    • 24. How do you handle global state in React? Compare Context, Redux, Zustand.
    • 25. What are portals and when would you use them?
    • 26. How do you handle authentication in a React app?
    • 27. Explain React Router's architecture. How do nested routes work?
    • 28. What is hydration in React? When does it happen?
    • 29. How do you handle side effects that depend on props/state?
    • 30. Explain the render props pattern. Is it still relevant?
    • 31. How do you debug React performance issues?
    • 32. What is the difference between forwardRef and useImperativeHandle?
    • 33. How do you handle data fetching in React? Compare useEffect, React Query, SWR.
    • 34. Explain concurrent rendering in React 18.
    • 35. How do you type React components with TypeScript?

    Advanced & Real-World Questions (Q36-Q50)

    36. Design a complex form with validation, conditional fields, and array fields.

    Expert Answer:

    Architecture decisions:

    • State management: useReducer for complex form state, or React Hook Form for performance
    • Validation: Zod/Yup schema with real-time and submit-time validation
    • Field arrays: Handle add/remove/reorder with stable keys
    {`// Using React Hook Form + Zod
    const schema = z.object({
      name: z.string().min(2),
      email: z.string().email(),
      addresses: z.array(z.object({
        street: z.string(),
        city: z.string(),
      })).min(1),
    });
    
    function ComplexForm() {
      const { register, control, handleSubmit, formState: { errors } } = useForm({
        resolver: zodResolver(schema),
      });
    
      const { fields, append, remove } = useFieldArray({
        control,
        name: "addresses",
      });
    
      return (
        
    {errors.name && {errors.name.message}} {fields.map((field, index) => (
    ))}
    ); }`}

    37. How would you implement infinite scroll with React?

    Expert Answer:

    {`function InfiniteList() {
      const [items, setItems] = useState([]);
      const [page, setPage] = useState(1);
      const [hasMore, setHasMore] = useState(true);
      const observerRef = useRef();
      const lastItemRef = useRef();
    
      // Intersection Observer for detecting scroll to bottom
      useEffect(() => {
        observerRef.current = new IntersectionObserver(
          (entries) => {
            if (entries[0].isIntersecting && hasMore) {
              setPage(p => p + 1);
            }
          },
          { threshold: 1.0 }
        );
    
        if (lastItemRef.current) {
          observerRef.current.observe(lastItemRef.current);
        }
    
        return () => observerRef.current?.disconnect();
      }, [hasMore, items.length]);
    
      // Fetch data when page changes
      useEffect(() => {
        fetchItems(page).then(newItems => {
          setItems(prev => [...prev, ...newItems]);
          setHasMore(newItems.length === PAGE_SIZE);
        });
      }, [page]);
    
      return (
        
    {items.map((item, i) => (
    {item.content}
    ))} {hasMore && }
    ); }`}

    Production considerations:

    • Virtualization for very long lists (react-window)
    • Handle loading, error states
    • Debounce scroll events if using scroll listener
    • Consider using React Query for caching and deduplication

    38-50. More Advanced Questions:

    • 38. Design a real-time collaborative editor with React
    • 39. How would you implement optimistic updates?
    • 40. Explain React Server Components. How do they differ from SSR?
    • 41. How do you handle complex animations in React?
    • 42. Design a micro-frontend architecture with React
    • 43. How do you handle state persistence across page refreshes?
    • 44. Implement a drag-and-drop interface from scratch
    • 45. How would you build a design system with React?
    • 46. Explain React's fiber architecture
    • 47. How do you handle memory leaks in React applications?
    • 48. Design an offline-first React application
    • 49. How do you handle internationalization (i18n) in React?
    • 50. Build a complex data visualization dashboard with React

    Practice React Interviews

    Use Craqly to practice explaining React concepts and get real-time feedback on your technical communication.

    Common Mistakes Candidates Make

    ❌ What to Avoid:

    • • Over-optimizing with useMemo/useCallback everywhere
    • • Not understanding why hooks have rules
    • • Using index as key in dynamic lists
    • • Putting everything in global state
    • • Not cleaning up effects properly
    • • Mutating state directly

    ✓ What Works:

    • • Measure before optimizing
    • • Understand React's mental model
    • • Use stable, unique keys
    • • Colocate state near where it's used
    • • Always return cleanup from effects
    • • Treat state as immutable

    Pro Tips from Interviewers

    Think out loud about trade-offs

    "I could use Context here, but if this data changes frequently, Redux might be better because..." shows depth.

    Know the "why" behind patterns

    Don't just know how to use React.memo—explain when it helps and when it doesn't.

    Mention edge cases

    "This works, but we'd need to handle the loading state, errors, and what happens if the component unmounts during the fetch."

    Share this article
    C

    Written by

    Craqly Team

    Comments

    Leave a comment

    No comments yet. Be the first to share your thoughts!

    Ready to Transform Your Interview Skills?

    Join thousands of professionals who have improved their interview performance with AI-powered practice sessions.