Skip to content
GeeksSmith
intermediate 6 min read

useMemo, useCallback & React.memo Internals

`useMemo` caches the result of an expensive calculation; `useCallback` caches a function definition between renders to preserve referential equality when passed to memoized children.

Why it matters in interviews

Premature or incorrect memoization wastes memory and adds comparison overhead, whereas missing referential stability in props breaks `React.memo` and triggers cascading re-renders across large component trees.

Visual & Interactive Explanation

useMemo vs useCallback Decision Matrix

Interactive Comparison

useMemo

Cache computed return value

Computed Value
ReturnsCalculated result value
SyntaxuseMemo(() => compute(a,b), [a,b])
Primary UseExpensive transformations / Derived objects
Strengths
  • Prevents redundant CPU calculation
  • Preserves object referential equality
Trade-offs
  • Memory overhead for cached value and dependencies

useCallback

Cache function reference

Function Reference
ReturnsThe function itself
SyntaxuseCallback((arg) => doWork(arg), [deps])
Primary UseEvent handlers passed to React.memo children
Strengths
  • Prevents child re-renders with React.memo
  • Stable effect dependency
Trade-offs
  • Does not prevent parent component from re-rendering

React.memo

Component-level shallow diff

Component HOC
WrapsReact Component
ComparisonShallow diffs prevProps vs nextProps
Primary UseLarge leaf components with pure rendering
Strengths
  • Skips entire child render subtree
Trade-offs
  • Fails if any prop reference changes

Code Examples & Implementation

Fixing Broken React.memo with useCallback
import React, { useState, useCallback, memo } from 'react';

// Child component wrapped in memo (only re-renders if props change referentially)
const ExpensiveChart = memo(({ onPointClick }: { onPointClick: (id: string) => void }) => {
  console.log('Rendering 1,000 chart nodes...');
  return <div onClick={() => onPointClick('point-1')}>Interactive Chart</div>;
});

export function ParentDashboard() {
  const [count, setCount] = useState(0);
  const [filter, setFilter] = useState('all');

  // ✅ Stable callback reference across renders of ParentDashboard
  const handlePointClick = useCallback((id: string) => {
    console.log('Selected point:', id);
  }, []); // Empty deps because it doesn't close over parent state

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Re-render Parent ({count})</button>
      {/* ExpensiveChart will NOT re-render when count updates! */}
      <ExpensiveChart onPointClick={handlePointClick} />
    </div>
  );
}

Interview-Ready Answers

`useMemo` memoizes a computed value: `useMemo(() => fn(), deps)`. `useCallback` memoizes a callback function: `useCallback(fn, deps)`. `useCallback(fn, deps)` is syntactic sugar for `useMemo(() => fn, deps)`.

Official Documentation & Specifications

Follow-up Questions & Deep Dives

🎯 Code Output — What Does This Print?

Question 1medium
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
  console.log("Child render");
  return <button onClick={onClick}>Click</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => console.log("clicked");

  console.log("Parent render");
  return (
    <>
      <p>{count}</p>
      <Child onClick={handleClick} />
      <button onClick={() => setCount((c) => c + 1)}>+</button>
    </>
  );
}
// User clicks + button. What logs?
Question 2hard
function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  const expensiveValue = useMemo(() => {
    console.log("computing...");
    return count * 2;
  }, [count]);

  console.log("render:", expensiveValue);
  return (
    <>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={() => setCount((c) => c + 1)}>+</button>
    </>
  );
}
// User types "abc" (3 keystrokes). What logs?
Question 3tricky
function App() {
  const [count, setCount] = useState(0);

  const doubled = useMemo(() => count * 2, [count]);
  const handleLog = useCallback(() => {
    console.log("doubled:", doubled);
  }, []);

  return <button onClick={handleLog}>Log (count={count})</button>;
}
// User clicks + twice (count → 2), then clicks Log button.

Common Mistakes & Anti-Patterns

  • Wrapping trivial calculations (e.g. `useMemo(() => a + b, [a, b])`) where the hook overhead exceeds the computation
  • Omitting dependencies causing stale closures where functions operate on old state
  • Using useCallback without wrapping the receiving child in React.memo (rendering is not prevented)

Real-World Architectural Scenario

A large analytics grid with 500 rows re-renders whenever a global clock tick updates every second. How do you optimize it?

Rate Your Readiness

Rate how comfortably you can explain this in an interview to update your global readiness gauge:

Rate your confidence: