React useMemo vs useCallback
One of the most frequent React interview questions is: What is the difference between useMemo and useCallback?
While both hooks are used for performance optimization through memoization (caching), they serve different purposes:
useMemocaches the result of a function calculation.useCallbackcaches the function definition itself.
Let's look at a quick visual comparison before we explore how they work.
The Calculator Analogy
useMemois like a calculator with memory. If you ask it to compute a really long equation, it does the math once and saves the result. The next time you ask for the same equation, it reads the result from memory instead of doing the math again.useCallbackis like keeping the same copy of a printed recipe. Instead of printing a brand new copy of the recipe every single time you cook (render), you just reuse the exact same sheet of paper (the function reference) to save paper and time.
1. Value Memoization: useMemo
By default, everything inside a React component runs again when the component re-renders. This includes calculations.
The Problem: Unnecessary Calculations
Imagine you have a list of items that you filter based on search input.
import { useState } from "react";
function ProductList({ products }) {
const [query, setQuery] = useState("");
// This runs on EVERY single render, even if products and query haven't changed!
// If the list is large, this will slow down typing in the input.
const filteredProducts = products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase()),
);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
{filteredProducts.map((p) => (
<div key={p.id}>{p.name}</div>
))}
</div>
);
}The Solution: Using useMemo
We can wrap the filtering logic in useMemo. React will only re-run the filter function when products or query changes.
import { useState, useMemo } from "react";
function ProductList({ products }) {
const [query, setQuery] = useState("");
// React runs this function once and caches the array.
// It only runs again if 'products' or 'query' changes.
const filteredProducts = useMemo(() => {
return products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase()),
);
}, [products, query]); // Dependency array
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
{filteredProducts.map((p) => (
<div key={p.id}>{p.name}</div>
))}
</div>
);
}2. Reference Memoization: useCallback
To understand useCallback, you must understand how JavaScript compares objects and functions.
In JavaScript, functions are objects. When you compare two functions, JavaScript compares their memory references (where they are saved in memory), not what they do.
const functionOne = () => console.log("hello");
const functionTwo = () => console.log("hello");
console.log(functionOne === functionTwo); // falseEvery time a React component renders, all functions defined inside it are recreated from scratch. They get a brand-new reference.
The Problem: Unnecessary Child Re-renders
If you pass a function as a prop to a child component, the child component will see a new prop on every render, even if the function code is identical.
If the child component is wrapped in React.memo (which skips rendering if props don't change), this optimization is broken because the function reference changes on every render.
import React, { useState } from "react";
// Child component is optimized using React.memo
const ChildButton = React.memo(({ onClick }) => {
console.log("ChildButton rendered!");
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// This function gets recreated on every Parent render.
// It gets a brand new memory reference.
const handleClick = () => {
console.log("Button clicked");
};
return (
<div>
<button onClick={() => setCount(count + 1)}>
Parent Click Count: {count}
</button>
{/* ChildButton will re-render EVERY time because handleClick is a new reference */}
<ChildButton onClick={handleClick} />
</div>
);
}The Solution: Using useCallback
By wrapping the handler in useCallback, React returns the exact same function reference across renders.
import React, { useState, useCallback } from "react";
const ChildButton = React.memo(({ onClick }) => {
console.log("ChildButton rendered!");
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// React caches this function definition.
// The reference stays the same across renders.
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []); // Empty dependencies mean reference never changes
return (
<div>
<button onClick={() => setCount(count + 1)}>
Parent Click Count: {count}
</button>
{/* ChildButton will NOT re-render because handleClick reference is stable */}
<ChildButton onClick={handleClick} />
</div>
);
}3. How they Relate Internally
Under the hood, useCallback is just a shortcut (syntax sugar) for useMemo returning a function.
The following two lines of code do the exact same thing:
// Caches the function definition itself
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
// Caches the value returned by the function (which is another function)
const handleClick = useMemo(() => {
return () => {
console.log("Clicked");
};
}, []);4. Key Differences Summary
| Feature | useMemo | useCallback |
|---|---|---|
| What it caches | The result of a function (value, array, object). | The function definition itself. |
| What it returns | The calculated value. | The stable function reference. |
| Why use it | To avoid running slow calculations again. | To prevent child components from re-rendering. |
| Common companion | Standard JavaScript arrays and objects. | React.memo on the child component. |
5. When NOT to Memoize (Over-optimization)
Do not use useMemo and useCallback everywhere. They have costs:
- Initial setup cost: React needs to register the hook and store the initial values in memory.
- Comparison cost: On every render, React must loop through the dependency array and do a shallow comparison (
Object.is) to check if any dependency changed.
Example of Useless useMemo
// BAD: Adding numbers is extremely fast. The hook overhead is larger than the computation.
const total = useMemo(() => a + b, [a, b]);
// GOOD: Simple variables are cheap. Keep them raw.
const total = a + b;Example of Useless useCallback
// BAD: The button is a standard HTML tag. It will re-render anyway.
// Caching the reference here serves no purpose.
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
return <button onClick={handleClick}>Click</button>;Common Interview Questions
1. Does useCallback make a function run faster?
No. useCallback does not speed up the code inside the function. It only makes sure that the function keeps the same memory address (reference) between renders, which helps optimized child components avoid re-rendering.
2. What happens if you forget the dependency array?
If you forget the dependency array, the hook will run and recreate the cached value or function reference on every single render. This defeats the purpose of the hook and adds unnecessary overhead.
// BAD: Recomputes on every render
const value = useMemo(() => expensiveFunction());
// GOOD: Only recomputes when dependencies change
const value = useMemo(() => expensiveFunction(), [dependency]);3. How do you decide whether to use useMemo or useCallback?
Ask yourself: What am I trying to cache?
- If you are trying to cache the output of a slow function (like filter, sort, map, or a complex math calculation), use
useMemo. - If you are trying to pass a function to a child component optimized with
React.memo, useuseCallback.
You can also use this simple decision flowchart:
