What is the useCallback hook in React?

React

The short answer

useCallback returns a memoized version of a function that only changes when its dependencies change. It is used to prevent unnecessary re-creation of functions on every render, which matters when you pass callbacks to child components that rely on reference equality to avoid re-rendering.

The problem it solves

In React, every time a component re-renders, all the functions inside it are re-created. Most of the time this is fine. But it becomes a problem when you pass a function as a prop to a child component that is wrapped in React.memo.

function Parent() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log('clicked');
};
return (
<>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveChild onClick={handleClick} />
</>
);
}
const ExpensiveChild = React.memo(({ onClick }) => {
console.log('ExpensiveChild rendered');
return <button onClick={onClick}>Click me</button>;
});

Every time Parent re-renders (when count changes), handleClick is re-created. Even though the function does the exact same thing, it is a new function in memory. React.memo compares props by reference, sees a new function reference, and re-renders ExpensiveChild anyway.

How useCallback fixes this

function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return (
<>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveChild onClick={handleClick} />
</>
);
}

Now handleClick is memoized. React returns the same function reference on every render (as long as the dependencies do not change). React.memo sees the same reference and skips re-rendering ExpensiveChild.

The dependency array

The second argument to useCallback is the dependency array. The function is only re-created when one of the dependencies changes.

function SearchForm({ onSearch }) {
const [query, setQuery] = useState('');
const handleSubmit = useCallback(() => {
onSearch(query);
}, [query, onSearch]);
return (
<form onSubmit={handleSubmit}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</form>
);
}

Here, handleSubmit is re-created whenever query or onSearch changes. If query changes from "react" to "vue", we need a new function that closes over the new value of query.

When to use useCallback

Use it when:

  • You pass a function to a child component wrapped in React.memo
  • You pass a function as a dependency of useEffect or another hook
  • The function creation is somehow expensive (rare)

Do NOT use it when:

  • The function is not passed to a child component
  • The child component is not memoized
  • You are just trying to "optimize" without measuring a real problem

Common Pitfalls

A very common mistake is wrapping every function in useCallback thinking it will improve performance. It does not. useCallback itself has a cost — React has to store the memoized function and compare dependencies on every render. If you are not passing the function to a memoized child, useCallback adds overhead without any benefit. Only use it when you have a measurable performance issue or when the pattern clearly calls for it.

Interview Tip

When explaining useCallback, always explain the problem first — why functions get re-created on every render and why that matters for memoized children. Then show how useCallback solves it. Interviewers want to see that you understand when to use it and, more importantly, when not to. Saying "I would only use useCallback when passing functions to memoized children" shows maturity.

Why interviewers ask this

useCallback is one of the hooks that candidates often misunderstand or overuse. Interviewers want to see if you know why it exists, when it actually helps, and when it is unnecessary. Understanding useCallback shows you think about performance in a measured way rather than blindly applying optimizations.