React Interview Prep
Lifecycle and Rendering

Re-rendering

When & Why

LinkedIn Hook

Your React component re-rendered 37 times. Only 2 of those actually changed something on the screen.

Most developers know that "state changes cause re-renders." But that is only one-third of the story. Parent re-renders, context changes, and force updates all trigger re-renders too — and understanding the difference between a re-render and a DOM update is what separates junior developers from senior ones.

Here is the part that surprises people: a re-render is cheap. React calling your function component again is fast. What is expensive is unnecessary DOM mutations — and React already prevents most of those through reconciliation. The real performance problem is not re-rendering. It is re-rendering components that run heavy computations or trigger cascading re-renders down a deep tree.

In interviews, you will get asked: "What triggers a re-render?" "Does a re-render mean the DOM updates?" "How do you find unnecessary re-renders?" The candidates who answer with precision — not just "state changes" — are the ones who stand out.

In this lesson, I cover every trigger for re-rendering, why re-renders are not the enemy, how to spot the ones that actually matter, and how to use React DevTools Profiler to prove it.

If you have ever wondered why your component keeps re-rendering even though nothing visibly changed — this one is for you.

Read the full lesson -> [link]

#React #JavaScript #InterviewPrep #Frontend #CodingInterview #ReactRendering #Performance #ReactDevTools #100DaysOfCode


Re-rendering thumbnail


What You'll Learn

  • The three triggers that cause a React component to re-render (state change, parent re-render, context change)
  • Why a re-render does not mean the DOM actually updates
  • How React's reconciliation prevents unnecessary DOM mutations
  • What "unnecessary re-renders" really means and when they actually matter
  • How to use React DevTools Profiler to identify re-render bottlenecks
  • The difference between re-rendering (cheap) and DOM updates (expensive)

The Concept — What Is a Re-render?

Analogy: The Restaurant Order Ticket

Think of a React component as a chef who receives order tickets. Every time a ticket comes in, the chef reads it and prepares the plate. That is a re-render — React calls your function component again and gets a new JSX description of what the UI should look like.

But here is the key: the chef does not automatically send the plate to the table. A manager (React's reconciliation engine) compares the new plate to what is already on the table. If the plate looks identical to what the customer already has, the manager says: "Do not bother — the table is fine." No food is sent. No waiter moves.

A re-render is the chef preparing the plate. A DOM update is the waiter delivering it to the table. They are two separate steps, and React skips the second one whenever the output has not changed.

This distinction is critical. Most performance advice on the internet conflates re-renders with DOM updates. They are not the same thing. Re-renders are fast (calling a JavaScript function). DOM updates are slow (browser layout and paint). React is already optimized to minimize the expensive part.


The Three Triggers for Re-rendering

There are exactly three things that cause a React component to re-render:

  1. State change — the component calls setState (or a useState setter)
  2. Parent re-render — the parent component re-renders, so all its children re-render too
  3. Context change — a context value the component consumes via useContext changes

That is the complete list. Props changing alone does not trigger a re-render — the parent re-rendering does (and the new props come along for the ride).


Trigger 1: State Change

When a component updates its own state, React schedules a re-render for that component.

Code Example 1: State Change Triggers Re-render

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  // This logs every time the component function executes (every re-render)
  console.log("Counter rendered, count:", count);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count)}>Set Same Value</button>
    </div>
  );
}

// Click "Increment":
//   Console: "Counter rendered, count: 1"
//   React re-renders because state changed (0 -> 1)
//   DOM updates because the output changed ("Count: 0" -> "Count: 1")
//
// Click "Set Same Value":
//   React MAY skip the re-render entirely (bailout optimization)
//   If count is already 3 and you call setCount(3), React sees the
//   value is the same (Object.is comparison) and can bail out.
//   Note: React may still re-render once to verify, but will skip
//   committing to the DOM.

Key detail: React uses Object.is to compare the old and new state. If they are the same, React can bail out early. This is why setting state to the same primitive value often skips the re-render. But setting state to a new object reference (even with identical content) will always trigger a re-render.


Trigger 2: Parent Re-render

When a parent component re-renders, all of its children re-render too — regardless of whether their props changed. This is the default behavior and the most common source of "unnecessary" re-renders.

Code Example 2: Parent Re-render Cascades to Children

import { useState } from "react";

function Parent() {
  const [count, setCount] = useState(0);

  console.log("Parent rendered");

  return (
    <div>
      <p>Parent count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment Parent</button>

      {/* Child receives NO props that depend on count */}
      <Child name="Alice" />
    </div>
  );
}

function Child({ name }) {
  // This logs EVERY time Parent re-renders — even though name never changes
  console.log("Child rendered, name:", name);

  return <p>Hello, {name}</p>;
}

// Click "Increment Parent":
//   Console: "Parent rendered"
//   Console: "Child rendered, name: Alice"
//
// Child re-renders because its parent re-rendered.
// The props did not change. The DOM output did not change.
// React called the Child function, compared the old and new JSX,
// found they were identical, and skipped the DOM update.
//
// The re-render happened. The DOM update did not.
// This is the distinction most developers miss.

Re-rendering visual 1


Trigger 3: Context Change

When a context value changes, every component that consumes that context via useContext re-renders — no matter how deep in the tree it sits.

Code Example 3: Context Change Triggers Re-render

import { useState, useContext, createContext } from "react";

// Create a theme context
const ThemeContext = createContext("light");

function App() {
  const [theme, setTheme] = useState("light");

  console.log("App rendered");

  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
        Toggle Theme
      </button>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  // Toolbar does NOT consume the context — but re-renders because App (parent) re-rendered
  console.log("Toolbar rendered");
  return <ThemedButton />;
}

function ThemedButton() {
  // This component CONSUMES the context — it re-renders when theme changes
  const theme = useContext(ThemeContext);
  console.log("ThemedButton rendered, theme:", theme);

  return (
    <button style={{ background: theme === "dark" ? "#333" : "#fff" }}>
      I am {theme}
    </button>
  );
}

// Click "Toggle Theme":
//   Console: "App rendered"              (state change in App)
//   Console: "Toolbar rendered"          (parent re-rendered)
//   Console: "ThemedButton rendered, theme: dark"  (context changed + parent re-rendered)
//
// ThemedButton would re-render even if Toolbar were wrapped in React.memo,
// because context changes bypass memo. This is a common interview gotcha.

Interview detail: Context changes bypass React.memo. Even if you memoize a component, if it reads from a context via useContext, it will re-render when that context value changes. This is by design — the component depends on that value, so it must re-render.


Re-render Does Not Mean DOM Update

This is the single most important concept in this lesson. Let me make it explicit with a diagram.

State Change / Parent Re-render / Context Change
            |
            v
   React calls your component function     <-- This is the "re-render"
            |
            v
   React gets new JSX (virtual DOM)
            |
            v
   React compares new JSX to previous JSX   <-- Reconciliation / Diffing
            |
       +---------+
       |         |
    Changed   Unchanged
       |         |
       v         v
  Update DOM   Do nothing                  <-- Only changed parts touch the DOM

The re-render is step 1: React calling your function. The DOM update is step 5, and it only happens for the parts that actually changed. Between those steps, reconciliation filters out everything that stayed the same.

Re-rendering visual 2


When Do "Unnecessary Re-renders" Actually Matter?

A re-render is unnecessary when the component produces the exact same output as before. But "unnecessary" does not always mean "problematic." Here is when you should and should not care:

Do not care (most cases):

  • The component is small and renders fast
  • The component does not run heavy calculations
  • The re-render tree is shallow

Care when:

  • The component runs expensive computations on every render (complex filtering, sorting large lists)
  • The component renders hundreds or thousands of items (virtualization territory)
  • The re-render cascades through a deep and wide subtree
  • You can measure a visible performance issue with DevTools Profiler

The rule: Do not optimize re-renders until you have measured a problem. Premature optimization of re-renders is one of the most common mistakes React developers make.


React DevTools Profiler — Proving It

The React DevTools Profiler is the tool you use to measure re-renders and identify bottlenecks. Here is how to use it in an interview-relevant way.

Code Example 4: Profiler Setup and Reading Results

import { useState, Profiler } from "react";

// The onRender callback receives timing information
function onRenderCallback(
  id,        // The "id" prop of the Profiler tree that just committed
  phase,     // "mount" (first render) or "update" (re-render)
  duration,  // Time spent rendering the committed update (ms)
  baseDuration, // Estimated time to render entire subtree without memoization
  startTime, // When React began rendering this update
  commitTime // When React committed this update
) {
  console.log(`[${id}] ${phase} — took ${duration.toFixed(2)}ms`);
}

function App() {
  const [count, setCount] = useState(0);

  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <div>
        <button onClick={() => setCount(count + 1)}>Count: {count}</button>
        <ExpensiveList />
      </div>
    </Profiler>
  );
}

function ExpensiveList() {
  // Simulates a slow component — renders 10,000 items
  console.log("ExpensiveList rendered");

  const items = [];
  for (let i = 0; i < 10000; i++) {
    items.push(<li key={i}>Item {i}</li>);
  }

  return <ul>{items}</ul>;
}

// Click the button:
//   Console: "[App] update — took 45.23ms"
//   Console: "ExpensiveList rendered"
//
// ExpensiveList re-renders every time count changes (parent re-rendered),
// even though it has no dependency on count.
// The Profiler tells you it took ~45ms — that is a measurable problem.
//
// Fix: wrap ExpensiveList in React.memo (covered in Lesson 4.3)
//   After memo: "[App] update — took 0.34ms"
//   ExpensiveList does NOT re-render — React skipped it.

Using React DevTools Profiler in the browser:

  1. Install the React DevTools browser extension
  2. Open DevTools and go to the "Profiler" tab
  3. Click the record button, interact with your app, then stop recording
  4. Each bar in the flame chart represents a component. Gray bars means "did not re-render." Colored bars show render time
  5. Look for components with high render times that re-render frequently — those are your optimization targets

Common Mistakes

Mistake 1: Thinking Props Changes Trigger Re-renders

// MISCONCEPTION: "Child re-renders because its props changed"
function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* Child receives a CONSTANT prop — but still re-renders */}
      <Child label="hello" />
    </div>
  );
}

function Child({ label }) {
  console.log("Child rendered"); // This logs on every parent re-render
  return <p>{label}</p>;
}

// The truth: Child re-renders because PARENT re-rendered.
// Props did not change. React does not check props before re-rendering
// a child — it re-renders all children by default.
// To skip re-rendering when props haven't changed, use React.memo.

Mistake 2: Optimizing Every Re-render Without Measuring

// OVER-OPTIMIZATION: wrapping everything in React.memo "just in case"
const Header = React.memo(function Header() {
  return <h1>My App</h1>;
});

const Footer = React.memo(function Footer() {
  return <footer>2026</footer>;
});

const Divider = React.memo(function Divider() {
  return <hr />;
});

// These components render in microseconds.
// Wrapping them in React.memo adds overhead (prop comparison on every render)
// for zero measurable benefit.
//
// Rule: Only use React.memo when you have measured a performance problem.
// The Profiler is your proof — if a component renders in < 1ms,
// do not bother memoizing it.

Mistake 3: Forgetting That Context Changes Bypass React.memo

const ThemeContext = createContext("light");

// Even though this is memoized, it WILL re-render when ThemeContext changes
const ThemedCard = React.memo(function ThemedCard({ title }) {
  const theme = useContext(ThemeContext); // This subscription bypasses memo
  console.log("ThemedCard rendered");

  return <div className={theme}>{title}</div>;
});

// If you want to prevent this, you need to restructure:
// Option 1: Move useContext to a parent and pass the value as a prop
// Option 2: Split the context so components only subscribe to what they need
// Option 3: Use a memoized selector pattern (e.g., use-context-selector library)

Interview Questions

Q: What are the three things that cause a React component to re-render?

Three triggers: (1) The component's own state changes via setState or a useState setter. (2) The component's parent re-renders — all children re-render by default regardless of whether their props changed. (3) A context value the component consumes via useContext changes. Note that props changing alone does not trigger a re-render — it is the parent re-rendering that does.

Q: Does a re-render mean the DOM gets updated?

No. A re-render means React calls the component function again to get new JSX. React then compares the new JSX with the previous JSX (reconciliation). Only the parts that actually changed get committed to the real DOM. If the output is identical, the DOM is not touched at all. Re-renders are cheap JavaScript function calls. DOM updates are the expensive part, and React already minimizes them.

Q: A child component re-renders even though its props have not changed. Why?

Because the parent re-rendered. React's default behavior is to re-render all children when a parent re-renders, regardless of whether their props changed. React does not compare props before deciding to re-render — that comparison only happens if the child is wrapped in React.memo. Without memo, every child in the subtree re-renders.

Q: How would you identify unnecessary re-renders in a React application?

Use the React DevTools Profiler. Record a session, interact with the app, then examine the flame chart. Look for components that re-render frequently with high render times. Gray bars indicate components that did not re-render. The Profiler also shows the reason each component re-rendered (state change, parent re-render, hooks changed). You can also use the "Highlight updates" option in React DevTools to visually see which components re-render on the page in real time.

Q: Does React.memo prevent re-renders caused by context changes?

No. React.memo only prevents re-renders caused by parent re-renders when the props have not changed. If a memoized component reads from a context via useContext, it will still re-render when that context value changes. Context subscriptions bypass memo entirely. To address this, you can split contexts, move the useContext call to a parent component, or use a selector library.


Quick Reference — Cheat Sheet

+-----------------------------------+---------------------------------------------+
| Concept                           | Key Point                                   |
+-----------------------------------+---------------------------------------------+
| State change                      | Component calls setState -> re-renders.     |
|                                   | Same value (Object.is) may bail out.        |
+-----------------------------------+---------------------------------------------+
| Parent re-render                  | Parent re-renders -> ALL children            |
|                                   | re-render by default. Props do not matter.  |
+-----------------------------------+---------------------------------------------+
| Context change                    | Context value changes -> all consumers      |
|                                   | re-render. Bypasses React.memo.             |
+-----------------------------------+---------------------------------------------+
| Re-render vs DOM update           | Re-render = call the function (cheap).      |
|                                   | DOM update = commit changes (expensive).    |
|                                   | React only updates DOM for what changed.    |
+-----------------------------------+---------------------------------------------+
| Unnecessary re-renders            | Only a problem if measurable with Profiler. |
|                                   | Do not optimize without measuring first.    |
+-----------------------------------+---------------------------------------------+
| React DevTools Profiler           | Record -> interact -> read flame chart.     |
|                                   | Gray = did not render. Color = render time. |
+-----------------------------------+---------------------------------------------+
| React.memo                        | Skips re-render if props unchanged.         |
|                                   | Does NOT block context-triggered renders.   |
+-----------------------------------+---------------------------------------------+
| Profiler component                | <Profiler id="X" onRender={cb}> for         |
|                                   | programmatic render timing measurement.     |
+-----------------------------------+---------------------------------------------+

RULE: Re-render is not the enemy. Unnecessary DOM updates are.
RULE: Measure before you optimize — use the Profiler.
RULE: Props changing does not trigger re-renders. Parent re-rendering does.

Previous: Lesson 4.1 — Component Lifecycle with Hooks -> Next: Lesson 4.3 — React.memo — Preventing Unnecessary Re-renders ->


This is Lesson 4.2 of the React Interview Prep Course — 10 chapters, 42 lessons.

On this page