Lesson 70 of 81
React's cache() memoizes any function's result for the lifetime of one render pass — the same deduplication trick Next.js's fetch cache gives you for free, but for functions that don't call fetch at all.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Next.js automatically deduplicates identical fetch(url, options) calls made during a single render — call the same URL twice while rendering a page, and only one request actually goes out. That's convenient, but it only applies to fetch. The moment your data layer goes through an ORM, a database driver, or any function that isn't literally the fetch API, that automatic deduplication stops applying.
React's cache() function closes that gap. It wraps any function and memoizes its return value per render pass, keyed by its arguments — call the wrapped function five times with the same arguments during one render, and the real work runs once; the other four calls get the cached result instantly. The cache is scoped to a single request/render; it's cleared before the next one starts, so it never leaks stale data between users.
// lib/get-user.ts
import { cache } from "react";
import { db } from "./db";
export const getUser = cache(async (id: string) => {
console.log("hitting the database for", id); // runs once per render, not per call
return db.user.findUnique({ where: { id } });
});
// app/profile/layout.tsx
import { getUser } from "@/lib/get-user";
export default async function ProfileLayout({
children,
}: {
children: React.ReactNode;
}) {
const user = await getUser("u_123");
return (
<div>
<Header name={user.name} />
{children}
</div>
);
}
// app/profile/page.tsx
import { getUser } from "@/lib/get-user";
export default async function ProfilePage() {
const user = await getUser("u_123"); // same id — reuses the cached result
return <Bio bio={user.bio} />;
}
Both the layout and the page call getUser("u_123") during the same render, but "hitting the database for u_123" only logs once. Without cache(), that's two real database round-trips for data you already had.
cache() and fetch caching solve overlapping but distinct problems. fetch's automatic caching is Next.js-specific, works only on fetch calls, and can persist data across requests depending on the cache configuration. cache() is plain React, works on any function, and only lives for one render pass — it never persists between requests. Confusing the two leads to wrong assumptions about how long data actually sticks around.cache() is a request-scoped memoization, not a real cache. There's no TTL, no invalidation API, no size limit to reason about — it's just "don't repeat this exact call again during this one render." Reaching for it as a substitute for fetch's revalidation or on-demand revalidation misunderstands what it does.cache() doesn't do anything meaningful in Client Components or across a client-server boundary — the render pass it's scoped to is the server's render of that request, not the browser's component tree.This is the same idea as memoizing an expensive pure function — same input, same output, so skip the recomputation — just scoped to "one server render" instead of "one process lifetime." It's the data-fetching sibling of useMemo: both trade a bit of bookkeeping for skipping repeated work, but useMemo memoizes across a client component's re-renders while cache() memoizes across every Server Component that calls the same function during one request.