
Memoization in JavaScript: Optimising Function Calls

When writing JavaScript, we sometimes encounter situations where functions are called repeatedly with the same inputs, potentially performing expensive calculations each time. This repetition wastes resources, slows down our applications, and makes our code less efficient.
Memoization is a straightforward technique that solves this problem by caching results. In simple terms, it 'remembers' the outcome of previous function calls, so future calls with the same inputs can return instantly without recalculating anything.
In this article, I intend to explain clearly what memoization is, show you how it works, and demonstrate how you can use it practically to improve your JavaScript functions. And yes, 'memoization' might look suspiciously like an American spelling, but don't worry, there's no 'memoisation' version of the word to worry about. Even British developers have agreed to leave this one alone.
What Exactly is Memoization?
Memoization is simply a method of caching the output of functions. If you call a memoized function with the same arguments again, it quickly retrieves the stored result rather than recalculating it from scratch.
It's particularly useful when working with computationally expensive or recursive functions, as it reduces unnecessary repeated work.
A Practical Example of Memoization
Imagine we have a function that calculates Fibonacci numbers, a classic example that's very slow without optimisation. Here's a simple, unoptimised Fibonacci function:
const fibonacci = (n: number): number => { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2);};If you call fibonacci(40), you'll quickly notice that it is extremely slow. Why? Because it repeats the same calculations repeatedly.
We can optimise this using memoization.
Optimising with Memoization
Here's the same Fibonacci function, now optimised clearly with memoization:
const fibonacciMemoized = () => { const cache: Record<number, number> = {}; const fib = (n: number): number => { if (n <= 1) return n; if (cache[n]) return cache[n]; cache[n] = fib(n - 1) + fib(n - 2); return cache[n]; }; return fib;};const fibonacci = fibonacciMemoized();console.log(fibonacci(40)); // Much faster nowHow Memoization Works Clearly Explained
Memoization essentially does two simple things:
- Checks if the result for given inputs is already stored in the cache.
- If it's there, it returns the cached value immediately.
- If it's not cached, it calculates, stores, and returns the new result.
This approach ensures that expensive computations are only performed once, significantly improving performance for repeated function calls.
When Does Memoization Actually Help?
Memoization isn't always necessary, and can actually introduce overheads that slow your application down if applied to too many functions that simply don't need it. However, it really shines when:
- Your function takes noticeable time or resources to calculate.
- The function often runs with the same input arguments.
- Your inputs are straightforward (e.g., numbers, strings), making them easy to cache.
Practical uses include:
- Recursive algorithms (like our Fibonacci example).
- Heavy data transformations or maths‑heavy calculations.
- Caching responses from APIs or database queries.
If your function already runs quickly, adding memoization will probably not help much and might even complicate things unnecessarily.
Creating a Simple Memoization Utility
To make memoization convenient, we can create a reusable function like this:
const memoize = <T extends (...args: any[]) => any>(fn: T): T => { const cache = new Map<string, ReturnType<T>>(); return ((...args: Parameters<T>) => { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }) as T;};// Example useconst slowMultiply = (a: number, b: number) => { console.log('Calculating...'); return a * b;};const fastMultiply = memoize(slowMultiply);console.log(fastMultiply(2, 3)); // 'Calculating...' and then 6console.log(fastMultiply(2, 3)); // instantly returns 6 from cacheThis is not dissimilar to how React does it...
Memoization in React
If you're building applications with React, you've probably encountered cases where components unnecessarily re‑render, causing slow performance or laggy user interfaces. Thankfully, React provides built‑in memoization tools to tackle this issue directly; namely useMemo and useCallback.
Using useMemo
useMemo lets us cache the results of expensive calculations within components. Here's an example:
import { useMemo } from 'react';const MyComponent = ({ data }: { data: number[] }) => { const expensiveCalculation = useMemo(() => { console.log('Calculating...'); return data.reduce((sum, value) => sum + value, 0); }, [data]); return <div>Total: {expensiveCalculation}</div>;};With expensiveCalculation wrapped in useMemo, the calculation will only run a second time when the data prop changes. Otherwise, React instantly returns the cached result instead.
Using useCallback
useCallback is similar, but it memoizes entire functions rather than their results. This stops React from unnecessarily re‑creating functions on every render, which can be particularly useful when passing functions down as props:
import { useCallback } from 'react';const ButtonComponent = ({ onClick }: { onClick: () => void }) => ( <button onClick={onClick}>Click me</button>);const ParentComponent = () => { const handleClick = useCallback(() => { console.log('Button clicked!'); }, []); // function never changes return <ButtonComponent onClick={handleClick} />;};With useCallback, React understands that the handleClick function should remain stable across renders, avoiding unnecessary re‑renders of ButtonComponent.
Using React's memo()
Taking things one step further still, another tool that React provides for memoization is the memo() function, which lets you memoize entire components. Wrapping a component in memo() tells React to skip unnecessary re‑renders of the entire component if the props haven't changed.
For example:
import { memo } from 'react';type MyComponentProps = { value: number;};const MyComponent = ({ value }: MyComponentProps) => { console.log('Rendering MyComponent'); return <div>Value is {value}</div>;};export default memo(MyComponent);With this, React only re‑renders MyComponent if its props actually change. This approach is particularly helpful for components that don't often change but find themselves caught in frequent re‑render cycles due to changes higher up in the component tree.
This simple wrapper can noticeably improve your app's performance without complicating your code.
When to Use These Hooks
You won't always need these hooks. But if you notice:
- Slow or jerky UI when components re‑render repeatedly.
- Heavy calculations running every render.
- Child components frequently re‑rendering unnecessarily.
That's exactly the moment to bring in React's memoization hooks.
Does Memoization Improve Performance Significantly?
Memoization can give you a big speed boost, but there's always a trade‑off:
Speed improvement:
Future calls with identical arguments become practically instant.Memory usage:
Results are stored, slightly increasing memory usage.
Most of the time, this extra memory use is minimal and worth the performance gain. But always keep an eye on memory if you're memoizing large datasets.
Wrapping up
Memoization is genuinely useful for making slow, repetitive tasks run much faster. By caching previous function results, we reduce wasted effort and speed up our apps significantly. Once you're comfortable with memoization, you'll spot plenty of opportunities to optimise your code clearly and effectively.
Key Takeaways
- Memoization caches function results to prevent redundant calculations.
- It's perfect for expensive functions with repeated calls.
- Keep an eye on memory use—memoization adds slight overhead.
- It's simple and easy to implement with a reusable utility.
With this clear understanding of memoization, you'll be able to make your JavaScript code faster and smoother in no time.
Related Articles

Dynamic Programming in LeetCode: Solving 'Coin Change'. 
Understanding and Using Flexbox in CSS. Understanding and Using Flexbox in CSS

Topological Sort: Solving the 'Course Schedule' Problem. Topological Sort: Solving the 'Course Schedule' Problem

Understanding CSS Positioning. Understanding CSS Positioning
Use Chrome's Developer Tools to Track Element Focus. Use Chrome's Developer Tools to Track Element Focus

Best Practices for Vue Router in Large Applications. Best Practices for Vue Router in Large Applications

Using Vue's Teleport for Modals and Portals. Using Vue's Teleport for Modals and Portals

Intercepting Clipboard Events with JavaScript. Intercepting Clipboard Events with JavaScript

Understanding Element Dimensions in JavaScript: Width and Height. Understanding Element Dimensions in JavaScript: Width and Height

How to Import All Named Exports from a JavaScript File. How to Import All Named Exports from a JavaScript File

Extends and super in JavaScript Classes. extendsandsuperin JavaScript Classes
3Sum in JavaScript: Two Pointers After Sorting. 3Sum in JavaScript: Two Pointers After Sorting