Skip to main content
Arthur Ha

Performance

Stop Computing the Same Thing Twice - Cache Your Function Results

January 14, 2026

You're rendering a feed of 50 notifications. Each shows "2 hours ago" or "yesterday" - formatted from a timestamp. You call formatRelativeTime(notification.createdAt) inside the map. Clean.

Except 30 of those notifications were created in the same hour. You're running formatRelativeTime 50 times when you only need ~15 unique results. On every scroll, every re-render - same work, again.

That's redundant computation. The fix is memoization - caching results so the same input returns the cached output instead of recomputing.

The problem: compute on every call

TSX
function NotificationFeed({
  notifications,
}: {
  notifications: Notification[];
}) {
  return (
    <ul>
      {notifications.map((notif) => {
        const label = formatRelativeTime(notif.createdAt); // 50 calls, many duplicates
        return (
          <li key={notif.id}>
            <span>{label}</span>
            {notif.message}
          </li>
        );
      })}
    </ul>
  );
}

Every render, every item - formatRelativeTime runs again. No memory of previous results.

The fix: a module-level Map

TSX
const relativeTimeCache = new Map<number, string>();

function cachedFormatRelativeTime(timestamp: number): string {
  if (relativeTimeCache.has(timestamp)) {
    return relativeTimeCache.get(timestamp)!;
  }
  const result = formatRelativeTime(timestamp);
  relativeTimeCache.set(timestamp, result);
  return result;
}

function NotificationFeed({
  notifications,
}: {
  notifications: Notification[];
}) {
  return (
    <ul>
      {notifications.map((notif) => {
        const label = cachedFormatRelativeTime(notif.createdAt); // Once per unique timestamp
        return (
          <li key={notif.id}>
            <span>{label}</span>
            {notif.message}
          </li>
        );
      })}
    </ul>
  );
}

Now each unique timestamp is formatted once. The Map works everywhere - components, event handlers, utilities. No React dependency.

For single-value functions (e.g. auth checks):

TSX
let isLoggedInCache: boolean | null = null;

function isLoggedIn(): boolean {
  if (isLoggedInCache !== null) return isLoggedInCache;
  isLoggedInCache = document.cookie.includes("auth=");
  return isLoggedInCache;
}

function onAuthChange() {
  isLoggedInCache = null; // Clear when auth changes
}

When to use React's cache() instead

React 18+ has a built-in cache() for Server Components. It memoizes, but with different rules:

Module-level MapReact cache()
WhereClient, server, anywhereServer Components only
LifetimeUntil you clear itPer request (auto-invalidated)
Use casePure utils, repeated sync workRSC data fetching, request-scoped dedup
Call siteComponents, handlers, utilitiesMust be called from a Server Component

Use cache() when: You're in a Server Component and want to deduplicate fetches or heavy work across the same request. Multiple components might fetch the same user - cache() ensures the DB is hit once:

TSX
import { cache } from "react";

const getUser = cache(async (id: string) => {
  const res = await db.query("SELECT * FROM users WHERE id = $1", [id]);
  return res.rows[0];
});

async function ProfileHeader({ userId }: { userId: string }) {
  const user = await getUser(userId);
  return <h1>{user.name}</h1>;
}

async function ProfileSidebar({ userId }: { userId: string }) {
  const user = await getUser(userId); // Same request → cached, no second query
  return <aside>{user.bio}</aside>;
}

React invalidates this cache on every request - no stale data between users. Perfect for server-side data fetching.

Use a Map when: You need caching in Client Components, event handlers, or shared utilities. The Map persists until you clear it, so you control invalidation (e.g. on auth change, logout, or when data goes stale).

Rule of thumb: Server Components doing request-scoped work (especially async) → cache(). Pure functions called repeatedly with the same inputs → module-level Map (and invalidate when the underlying data changes).