A Deep Dive Comparison Between useMemo And useCallback

March 07, 2022 - 6 minutes

One of the greatest things that we received in the world of React development is hooks. The introduction of React hooks back in 2019 enabled a totally new way of handling logic. Hooks significantly improved how we could implement, structure, isolate and share logic across different components.

Even to this day, 3 years later at the moment of writing this article, I’m discovering and learning new things about certain hooks or their internals. One of the things that recently intrigued my curiosity were two React hooks that I’ve been using quite frequently: useMemo and useCallback.

Their Purpose

The purpose behind both hooks is to improve React performance through memoization. In short, memoization is a caching technique that stores computed values based on the input. If the function is called with the same input, then it will skip the computation and return the cached value. If the input changes, it will recalculate the value and cache it.

Both useMemo and useCallback allow React developers to easily memoize certain entities, which can be used to potentially prevent component re-renders. The useCallback hook is specifically meant for memoizing callbacks, as the name suggests. The useMemo hook can be used for all the other, static entities like primitives and objects.

The Similarities

In practice, the useMemo and useCallback hooks don’t only share the same purpose but are also extremely similar in usage. Both of the hooks:

  • have to be called at the top-level of a component, just like all hooks for that matter.
  • accept a function as their first argument and an array of dependencies as their second argument.
  • return a memoized value that is referentially stable across renders based on the function that is provided.
  • recalculate and memoize the value based on the function when any changes are detected in the dependencies array.

Are There Differences?

So then the interesting question becomes: are there differences between useMemo and useCallback? If so, what are they exactly?

As established already earlier in this article, a small difference is that useCallback can only be used to memoize callbacks while useMemo is meant for basically all the other JavaScript entities. Their purpose and usage are basically the same, which make it feel like the different entities that they target is the only difference between the hooks.

But conceptually, it’s possible to mimic the behaviour of memoizing a function through useCallback using useMemo too. Although it would look weird, it’s possible to use useMemo and return a function from the factory function. Theoretically, that would be the same as using useCallback.

If that holds, it would also mean that there is no real difference between useCallback and useMemo except for convenience. So, the question becomes whether there is a difference between memoizing a function through the useCallback hook, by doing useCallback(() => doSomething()), and memoizing a function through the useMemo by returning a callback from the factory function, by doing useMemo(() => () => doSomething())?

The Deep Dive

In the official React hook docs, there is already a small note that informs us that useCallback(fn, deps) is equivalent to useMemo(() => fn, deps. However, this doesn’t provide us with a lot of information or context. Like, does this hold for every type of function, especially inline ones? Are there edge cases? For that, let’s dive into the implementation of both hooks.

The code for both hooks is located inside the react-reconciler package, specifically in the file with the new React Fiber implementation for hooks. In there, you’ll find the code for all the hooks for when they mount and when they have to be updated. To discover the differences between the two memoization hooks that we’re considering, we have to look at their update functions: updateCallback and updateMemo.

Let’s start with the update function of the useCallback hook:

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

A few things are going on here, but the most important thing is that a subsequent call to the useCallback hook is handled in the following way:

  1. The previous dependencies (state) that were used to call this hook are retrieved and compared to the current ones.
  2. If they’re the same, the hook will return the cached callback from the previous run.
  3. If they’re not the same, the function will cache the current callback together with the current dependencies inside hook.memoizedState. These will then potentially be re-used in subsequent calls.

Now let’s take a look at the update function implementation of useMemo:

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    // Assume these are defined. If they're not, areHookInputsEqual will warn.
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate(); // <--!!
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

If we compare the two update functions, we notice that they are almost identical. Both hooks go through the same procedure during subsequent calls to the hook. The only difference that we can spot is the value being memoized.

In the case of useCallback, the value being memoized is directly the first function argument. In the case of useMemo, the first function argument is also used but in a slightly different way. We can also see that it has a different name, nextCreate compared to callback. Instead of memoizing the provided function immediately, the hook invokes it and memoizes the resulting value.

Based on that, we can revisit the original question regarding whether there’s a difference between useCallback(() => doSomething()) and useMemo(() => () => doSomething()). If we focus on the latter, it means that the hook will invoke the factory function and cache the () => doSomething() function on mount. As long as the dependencies don’t change, it will keep using the cached version.

This is exactly what useCallback also does, but without invoking a factory function and taking the defined function instead. Because of how memoization is implemented in React hooks where it only compares to the previous invocation, this means that inline defined functions also work perfectly fine and exactly the same between both hooks.

Conclusion

This article compared two commonly used React hooks for memoization, useCallback and useMemo. Both of them stand at the foundation of performant React development. At first glance, both hooks are extremely similar to the extent that one can doubt whether there is any difference. Is there an actual difference or are they wrappers around the same logic with different names for convenience?

To answer this question, this article took a deep dive into the implementations of both hooks. There, we found that the logic behind both hooks is basically the same except for how the memoized value is determined. Considering the way this is implemented and the way memoization in React hooks is implemented, there’s no difference between memoizing an (inline) function normally using useCallback or returning it from useMemo.

Based on that, it can be concluded that there is no technical difference between both hooks and that 2 separate hooks only exist for the sake of convenience.


After graduation, my career is entirely centered around learning and improving as a developer. I’ve began working full time as a React developer and I’ll be blogging about everything that I encounter and learn during this journey. This will range from improving communicational skills in a technical environment, becoming a better developer, improving technical skills in React and JavaScript, and discussing career related topics. In all of my posts, the focus will be on my personal experiences, learnings, difficulties, solutions (if present), and also flaws.

If you’re either interested in these topics, more personalised technical stories, or the perspective of a learning developer, you can follow me either on Twitter or at Dev to stay up to date with my blogposts. I’m always learning, so stay tuned for more stories! 🎉