React.memo
Preventing Unnecessary Re-renders
LinkedIn Hook
Every time a parent component re-renders, all of its children re-render too. Even if their props have not changed. Even if they return the exact same JSX.
This is how React works by default — and for most apps, it is perfectly fine. React is fast. But when you have an expensive child component that renders a chart, a large list, or a complex form — re-rendering it 30 times per second because a parent's unrelated state changed is a real performance problem.
React.memosolves this. It tells React: "Only re-render this component if its props actually changed." One wrapper, and you skip every unnecessary render.But here is where developers get it wrong — they slap
React.memoon everything thinking it will make their app faster. It does not. Shallow comparison has a cost. Wrapping every component inmemoadds overhead without benefit. And if you pass objects or functions as props without memoizing them,React.memodoes nothing because the references change every render.In this lesson, I break down what
React.memoactually does, how shallow comparison works, when it genuinely helps, how to write a custom comparison function, and why combining it withuseCallbackanduseMemois the real pattern. I also cover the cases where you should NOT use it.If you have ever been asked "How do you prevent unnecessary re-renders in React?" in an interview — this is the complete answer.
Read the full lesson -> [link]
#React #JavaScript #InterviewPrep #Frontend #CodingInterview #ReactMemo #Performance #Optimization #100DaysOfCode
What You'll Learn
- What
React.memodoes and how it prevents unnecessary re-renders - How shallow comparison works on props (reference vs value equality)
- When
React.memogenuinely improves performance - How to write a custom comparison function for complex props
- Why combining
React.memowithuseCallbackanduseMemois essential - When NOT to use
React.memoand where it adds unnecessary overhead
The Concept — React.memo and Render Skipping
Analogy: The Security Guard at the Office Door
Imagine a busy office building. Every morning, every employee walks through the door and sits down at their desk — even the ones who have no new work to do. They still walk in, sit down, look at the same screen, and walk out. Wasted commute. Wasted energy.
Now you hire a security guard at the door. The guard checks: "Did anything change for this person since yesterday? New tasks? New assignments?" If nothing changed, the guard says: "You can skip coming in today. Your desk looks the same."
React.memo is that security guard. It wraps a component and checks: "Did the props change since the last render?" If they did not, React skips the render entirely and reuses the previous output. The component function never executes. No JSX created. No diffing needed.
Shallow comparison is the guard's checklist. The guard does not open every folder on your desk and compare page by page. He checks: "Same laptop? Same phone? Same badge?" If the objects look the same at the surface level (same references), he assumes nothing changed. This is fast but can be fooled — if you bring the same laptop bag with different contents inside, the guard waves you through.
The cost: Hiring the guard is not free. Every morning, he has to compare your items against yesterday's list. For employees who get new tasks every single day, the guard is wasted effort — he checks, finds a difference, and lets you in anyway. The comparison was pure overhead.
How React.memo Works
By default, when a parent re-renders, React re-renders all of its children. React.memo is a higher-order component that wraps a component and adds a prop comparison step before rendering.
Code Example 1: Basic React.memo Usage
import { useState, memo } from "react";
// Expensive component — renders a large list
const ExpensiveList = memo(function ExpensiveList({ items, title }) {
// This console.log only fires when props actually change
console.log(`Rendering ExpensiveList: ${title}`);
return (
<div>
<h2>{title}</h2>
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
});
function App() {
const [count, setCount] = useState(0);
const [items] = useState([
{ id: 1, name: "React" },
{ id: 2, name: "Vue" },
{ id: 3, name: "Angular" },
]);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Clicked: {count}
</button>
{/* ExpensiveList does NOT re-render when count changes */}
{/* because items and title references stay the same */}
<ExpensiveList items={items} title="Frameworks" />
</div>
);
}
// Output when button is clicked 3 times:
// Console: "Rendering ExpensiveList: Frameworks" (initial render only)
// Clicking the button does NOT trigger the console.log again.
// React.memo sees that items (same reference) and title (same string) have not changed.
Why this works: items is created with useState, so the reference stays the same across re-renders. title is a string literal, and primitive values are compared by value. Since neither changed, React.memo skips the re-render.
Shallow Comparison — How React.memo Checks Props
React.memo uses shallow comparison by default. This means it checks each prop with Object.is() — the same algorithm React uses for dependency arrays in hooks.
Shallow comparison rules:
- Primitives (string, number, boolean): compared by VALUE
"hello" === "hello" -> true (same value, skip re-render)
42 === 42 -> true
- Objects, arrays, functions: compared by REFERENCE
{} === {} -> false (different references, re-render!)
[] === [] -> false
() => {} === () => {} -> false
- Same reference:
const obj = { a: 1 };
obj === obj -> true (same reference, skip re-render)
Code Example 2: When React.memo Fails — New Object References
import { useState, memo } from "react";
const UserCard = memo(function UserCard({ user, onSelect }) {
console.log("Rendering UserCard");
return (
<div onClick={onSelect}>
<p>{user.name} — {user.role}</p>
</div>
);
});
function App() {
const [count, setCount] = useState(0);
// BUG: New object created every render — React.memo cannot help
const user = { name: "Alice", role: "Engineer" };
// BUG: New function created every render — React.memo cannot help
const handleSelect = () => console.log("Selected");
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
{/* UserCard re-renders EVERY time because user and onSelect are new references */}
<UserCard user={user} onSelect={handleSelect} />
</div>
);
}
// Output when button is clicked:
// Console: "Rendering UserCard" (every single click!)
// React.memo checks: is user === prevUser? NO — new object each render.
// The memo wrapper runs the comparison, finds a difference, and re-renders anyway.
// You paid the cost of comparison AND the cost of rendering. Worst of both worlds.
The fix: Move the object outside the component, or memoize it with useMemo. Memoize the function with useCallback. This is why React.memo almost always needs to be paired with useMemo and useCallback.
The Real Pattern — React.memo + useCallback + useMemo
Code Example 3: The Correct Way to Optimize
import { useState, memo, useCallback, useMemo } from "react";
const UserCard = memo(function UserCard({ user, onSelect }) {
console.log("Rendering UserCard");
return (
<div onClick={onSelect}>
<p>{user.name} — {user.role}</p>
</div>
);
});
function App() {
const [count, setCount] = useState(0);
const [name] = useState("Alice");
// useMemo: same reference unless name changes
const user = useMemo(() => ({ name, role: "Engineer" }), [name]);
// useCallback: same reference unless name changes
const handleSelect = useCallback(() => {
console.log(`Selected ${name}`);
}, [name]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
{/* NOW UserCard correctly skips re-renders when only count changes */}
<UserCard user={user} onSelect={handleSelect} />
</div>
);
}
// Output when button is clicked 3 times:
// Console: "Rendering UserCard" (initial render only)
// React.memo checks: is user === prevUser? YES — useMemo returns same reference.
// is onSelect === prevOnSelect? YES — useCallback returns same reference.
// Both props unchanged -> skip re-render.
This is the interview answer. React.memo alone is not enough. You need the full trio: React.memo on the child, useMemo for object/array props, and useCallback for function props. If you use React.memo without stabilizing the prop references, you get zero benefit and added overhead.
Custom Comparison Function
By default, React.memo uses shallow comparison. You can provide a custom comparison function as the second argument for more control.
Code Example 4: Custom arePropsEqual Function
import { memo } from "react";
const Chart = memo(
function Chart({ data, config }) {
console.log("Rendering Chart");
// Imagine an expensive D3 chart render here
return (
<div>
<p>Chart with {data.length} points</p>
<p>Theme: {config.theme}</p>
</div>
);
},
// Custom comparison: return true to SKIP re-render, false to RE-RENDER
// This is the OPPOSITE of shouldComponentUpdate (which returns true to update)
function arePropsEqual(prevProps, nextProps) {
// Only re-render if the data length changed or the theme changed
// Ignore other config properties like tooltipPosition or animation speed
return (
prevProps.data.length === nextProps.data.length &&
prevProps.config.theme === nextProps.config.theme
);
}
);
function Dashboard() {
// Even if config gets a new reference with changed tooltipPosition,
// the Chart only re-renders when data.length or config.theme changes
return (
<Chart
data={[1, 2, 3, 4, 5]}
config={{ theme: "dark", tooltipPosition: "top", animationSpeed: 300 }}
/>
);
}
// Key: arePropsEqual returns TRUE -> props are "equal" -> SKIP re-render
// arePropsEqual returns FALSE -> props are "different" -> RE-RENDER
// This is the opposite of the old shouldComponentUpdate!
When to use a custom comparison: When your component receives large objects but only cares about specific fields. When shallow comparison is too strict (re-renders too often) or too loose (does not catch nested changes you care about).
When NOT to Use React.memo
React.memo is not free. The comparison check runs on every parent re-render. If the component almost always receives new props, you are paying for the comparison and still re-rendering.
Do not use React.memo when:
-
The component is cheap to render. If it is a simple
<p>tag or a small div with text, the comparison cost outweighs the render cost. React's virtual DOM diffing is already very fast for small components. -
The props change on almost every render. If the parent passes new objects, arrays, or functions every time (and you cannot memoize them),
React.memowill always find a difference and re-render anyway. You paid for the comparison for nothing. -
The component accepts
children. Thechildrenprop is a new React element on every render. Wrapping a component that receiveschildreninReact.memoalmost never helps becausechildrenis always a new reference.
// This memo is useless — children is a new reference every render
const Card = memo(function Card({ children }) {
return <div className="card">{children}</div>;
});
// Parent:
<Card>
<p>Hello</p> {/* New JSX element created every render */}
</Card>
Common Mistakes
Mistake 1: Using React.memo Without Stabilizing Props
// BAD: memo is useless here — onClick is a new function every render
const Button = memo(function Button({ label, onClick }) {
console.log("Rendering Button");
return <button onClick={onClick}>{label}</button>;
});
function Form() {
const [value, setValue] = useState("");
return (
<div>
<input value={value} onChange={(e) => setValue(e.target.value)} />
{/* New arrow function every render — memo cannot help */}
<Button label="Submit" onClick={() => console.log(value)} />
</div>
);
}
// FIX: Wrap the handler in useCallback
// const handleClick = useCallback(() => console.log(value), [value]);
// <Button label="Submit" onClick={handleClick} />
Mistake 2: Confusing the Custom Comparison Return Value
// BAD: Returns true to mean "re-render" — this is BACKWARDS
const Item = memo(
function Item({ name }) {
return <p>{name}</p>;
},
(prevProps, nextProps) => {
// WRONG: true means "props are equal, SKIP re-render"
// Developer thinks true means "should update"
return prevProps.name !== nextProps.name; // Inverted logic!
}
);
// FIX: Return true when props ARE equal (skip re-render)
// return prevProps.name === nextProps.name;
Mistake 3: Wrapping Everything in React.memo "Just in Case"
// UNNECESSARY: These are trivial components with no performance concern
const Label = memo(({ text }) => <span>{text}</span>);
const Divider = memo(() => <hr />);
const Icon = memo(({ name }) => <i className={name} />);
// You now have comparison overhead on every render for components
// that take microseconds to render. This is premature optimization.
// Only use React.memo when you have measured a performance problem.
Interview Questions
Q: What is React.memo and what problem does it solve?
React.memois a higher-order component that memoizes a component's rendered output. When a parent re-renders, React normally re-renders all children regardless of whether their props changed.React.memoadds a shallow comparison of props before rendering — if the props have not changed, React skips the re-render and reuses the previous output. It solves the problem of unnecessary re-renders in expensive child components.
Q: How does React.memo compare props by default? What is shallow comparison?
By default,
React.memouses shallow comparison withObject.is()for each prop. Primitives (strings, numbers, booleans) are compared by value. Objects, arrays, and functions are compared by reference — not by their contents. This means two different objects with identical contents will fail the comparison ({a: 1} !== {a: 1}) because they are different references in memory. This is why you needuseMemoanduseCallbackto stabilize references when passing non-primitive props to memoized components.
Q: Why does React.memo often need to be combined with useCallback and useMemo?
Because
React.memocompares by reference for non-primitives. If a parent creates a new object or function on every render and passes it as a prop, the reference changes every time, andReact.memowill always detect a "change" and re-render.useMemostabilizes object and array references across renders.useCallbackstabilizes function references. Without them,React.memoprovides zero benefit — you pay for the comparison cost and still re-render every time.
Q: What is the difference between React.memo and useMemo?
React.memois a higher-order component that memoizes an entire component's render — it decides whether to re-render the component based on prop changes.useMemois a hook that memoizes a computed value inside a component — it caches the result of an expensive calculation and only recomputes when dependencies change.React.memoprevents re-renders.useMemoprevents re-computations. They solve different problems but often work together.
Q: When should you NOT use React.memo?
Do not use
React.memowhen: (1) the component is cheap to render — the comparison overhead is not worth it for simple components, (2) the props change on almost every render — you pay for comparison and still re-render, (3) the component receiveschildren— the children prop is always a new reference, making memo useless.React.memois a targeted optimization tool, not a default wrapper. Use it only after measuring that a specific component causes performance issues due to unnecessary re-renders.
Quick Reference — Cheat Sheet
+-----------------------------------+-------------------------------------------+
| Concept | Key Point |
+-----------------------------------+-------------------------------------------+
| React.memo(Component) | Wraps a component to skip re-renders |
| | when props have not changed. |
+-----------------------------------+-------------------------------------------+
| Shallow comparison (default) | Primitives: compared by value. |
| | Objects/arrays/functions: by reference. |
+-----------------------------------+-------------------------------------------+
| React.memo(Comp, arePropsEqual) | Custom comparison function. Return true |
| | to SKIP re-render, false to RE-RENDER. |
+-----------------------------------+-------------------------------------------+
| React.memo + useCallback | Stabilize function props so memo works. |
| | Without useCallback, functions are new |
| | references every render. |
+-----------------------------------+-------------------------------------------+
| React.memo + useMemo | Stabilize object/array props so memo |
| | works. Without useMemo, objects are new |
| | references every render. |
+-----------------------------------+-------------------------------------------+
| React.memo vs useMemo | React.memo: skips component re-render. |
| | useMemo: caches a computed value. |
+-----------------------------------+-------------------------------------------+
| When NOT to use | Cheap components, props always change, |
| | component accepts children. |
+-----------------------------------+-------------------------------------------+
RULE: React.memo alone is not enough — pair with useCallback and useMemo.
RULE: Only use React.memo when you have measured a real performance problem.
RULE: Custom comparison returns TRUE to skip, FALSE to re-render (opposite of shouldComponentUpdate).
Previous: Lesson 4.2 — Re-rendering — When & Why -> Next: Lesson 4.4 — Batching & Concurrent Features ->
This is Lesson 4.3 of the React Interview Prep Course — 10 chapters, 42 lessons.