React

useCallback vs useMemo: When Each One Actually Helps

Both hooks memoize things — but memoizing the wrong thing is worse than not memoizing at all. Here's the mental model that makes it click.

May 18, 2026
4 min read

Technologies Discussed

ReactHooksPerformance

The Confusion

Developers often sprinkle `useCallback` and `useMemo` everywhere "for performance" — and then wonder why their app is *slower*. Memoization has overhead. Used wrong, it costs more than it saves.

Here's the clear model.

useCallback: Memoize a Function Reference

tsx
const handleSubmit = useCallback(() => {
  submitForm(formData)
}, [formData])

`useCallback` returns the **same function object** across renders, as long as dependencies don't change.

**When it actually helps:** When you pass a callback to a child wrapped in `React.memo`. Without it, a new function is created on every render, and `React.memo`'s shallow comparison sees a "new" prop and re-renders anyway.

tsx
// Without useCallback — memo does nothing
const Parent = () => {
  const onClick = () => doSomething() // new reference every render
  return <MemoizedChild onClick={onClick} />
}

// With useCallback — memo works as intended const Parent = () => { const onClick = useCallback(() => doSomething(), []) return <MemoizedChild onClick={onClick} /> }

useMemo: Memoize a Computed Value

tsx
const sortedList = useMemo(() => {
  return items.sort((a, b) => a.name.localeCompare(b.name))
}, [items])

`useMemo` caches the **result of a computation** and only recomputes when dependencies change.

**When it actually helps:** Expensive calculations — filtering large datasets, complex transformations, or building derived state that would otherwise run on every keystroke.

The Litmus Test

Ask yourself two questions:

1. **Is this computation genuinely expensive?** (sorting 10,000 items = yes; mapping 5 items = no) 2. **Does this value/function flow into a memoized child?**

If the answer to *both* is no — skip the hook. You're adding complexity and overhead for no gain.

Common Mistake

tsx
// 🚫 Pointless — no memoized child, no expensive computation
const label = useMemo(() => `Hello, ${name}`, [name])

// ✅ Just write it const label = `Hello, ${name}`

Quick Reference

| Hook | Memoizes | Use when | |---|---|---| | `useCallback` | Function reference | Passing to a `React.memo` child | | `useMemo` | Computed value | Expensive calculation, or expensive object identity |

Takeaway

Don't memoize by default. Profile first, optimize second. When you *do* reach for these hooks, the rule is simple: **useCallback for functions, useMemo for values** — and only when there's a measurable reason to.

About the Author

Rajeev Ranjan Sinha is a full-stack engineer with 10+ years of experience building scalable web applications. He specializes in JavaScript/TypeScript, cloud architecture, and system design.

Get more articles like this

Subscribe to my newsletter for in-depth articles, quick tips, and insights on web development.