Skip to content

Expert · Functional patterns and testing · topic 49 of 61 · 30 min

Memoization, debounce and throttle

Explanation

Memoization caches a function's results so repeated calls with the same input are instant; it is safe only for pure functions. Debounce waits until calls stop for a moment before running once (perfect for a search box). Throttle runs at most once per time window (perfect for scroll or resize handlers).

Memoization: remember answers

If a pure function is slow and is called again and again with the same arguments, you can store each result in a cache (a Map) the first time and return the stored answer afterwards. This trade of memory for speed is called memoization.

javascript
function memoize(fn) {
  const cache = new Map();
  return (arg) => {
    if (cache.has(arg)) return cache.get(arg);
    const result = fn(arg);
    cache.set(arg, result);
    return result;
  };
}
const slowSquare = (n) => { for (let i = 0; i < 1e7; i++); return n * n; };
const fastSquare = memoize(slowSquare);
fastSquare(9); // slow the first time
fastSquare(9); // instant: read from the cache

Classic use: recursive Fibonacci becomes fast when each fib(n) is cached, going from exponential time to linear.

Careful: Only memoize pure functions. Caching an impure function such as getStockLevel(sku) returns stale data forever. Also limit the cache size for functions called with many different inputs, or memory grows without end (a memory leak).

Debounce: wait for a pause

A search box that calls the server on every key press sends 10 requests for "chicken bi". Debouncing delays the call until the user has stopped typing for, say, 300 ms, then calls once with the latest value. Each new call cancels the waiting one and restarts the timer.

javascript
function debounce(fn, waitMs) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), waitMs);
  };
}
const search = debounce((q) => console.log('Searching for', q), 300);
search('c'); search('ch'); search('chi'); search('chicken');
// after 300 ms of silence: Searching for chicken (only once)

Throttle: at most once per window

Throttling lets the function run immediately, then ignores calls until a time window has passed. Use it for events that fire continuously, like scroll (dozens of times per second), where you want regular updates but not every single one.

javascript
function throttle(fn, windowMs) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= windowMs) {
      last = now;
      fn(...args);
    }
  };
}
const onScroll = throttle(() => console.log('update progress bar'), 200);
// scrolling for 1 second calls onScroll ~60 times but logs about 5 times
TechniqueRunsTypical use
MemoizeOnce per unique input, then from cacheExpensive pure calculations
DebounceOnce, after calls stop for N msSearch box, auto-save, resize end
ThrottleAt most once every N msScroll, mouse move, game input
Think of it like this: Debounce is like a lift that waits until people stop entering for a few seconds before closing its doors. Throttle is like a bus that leaves every 10 minutes no matter how many people keep arriving. Memoization is like a teacher who writes the answer to a common question on the board so she never has to work it out again.
Tip: Libraries such as Lodash provide well-tested debounce and throttle with extra options (leading/trailing calls, cancel). Writing your own is a very common interview question, so learn the simple versions above by heart.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Memoization, debounce and throttle · 1/6

Memoize · debounce · throttle

In this lesson you will make code faster and calmer with memoization, debounce and throttle.

Prefer a video by a teacher? Find free videos on “javascript debounce vs throttle memoization interview” on YouTube ↗

In real life: A pincode lookup on a checkout page

A checkout page shows the city and state as the user types a 6-digit pincode. Without debounce, typing "560001" would call the pincode API six times. With a 300 ms debounce it calls once. Results are memoized, so if the user edits and returns to the same pincode, no new request is needed.

javascript
const lookups = [];
const cityFor = (pin) => { lookups.push(pin); return pin.startsWith('56') ? 'Bengaluru' : 'Unknown'; };
const memo = new Map();
const cachedCity = (pin) => memo.get(pin) ?? (memo.set(pin, cityFor(pin)), memo.get(pin));
cachedCity('560001'); cachedCity('560001'); cachedCity('560001');
console.log(lookups.length); // 1

Practice (0/3 done)

Task 1 · Code · Javascript

Write memoize(fn) for one-argument functions. The test counts how many times the real function runs.

Task 2 · Code · Javascript

Write debounce(fn, waitMs). Calls made within waitMs of each other should result in ONE call of fn, with the latest arguments, waitMs after the last call.

Task 3 · Code · Javascript

Write throttle(fn, windowMs, now = Date.now). fn runs on the first call, then calls are ignored until windowMs has passed since the last run. The now parameter lets the test use a fake clock.

Quick quiz

Question 1

1.Which suits a search box that calls the server?

Question 2

2.Which suits updating a progress bar while scrolling?

Question 3

3.When is memoization unsafe?

Question 4

4.In debounce, what does clearTimeout(timer) do on each call?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which suits a search box that calls the server?

Answer: Debounce

Debounce waits until typing pauses, then calls once.

Which suits updating a progress bar while scrolling?

Answer: Throttle

Throttle gives regular, limited updates during continuous events.

When is memoization unsafe?

Answer: For functions whose result depends on changing outside data

Cached results would be stale.

In debounce, what does clearTimeout(timer) do on each call?

Answer: Cancels the previously scheduled call

Each new call cancels the waiting one and restarts the wait.

A search box calls the server on every letter typed. What fixes this?Related

Answer: Debouncing the search so it runs after the user pauses

Debounce waits for a pause in typing and then runs once with the latest text.

From Timers: running code later

What is the time of fib(n) with memoization?Related

Answer: O(n)

Each value from 0 to n is computed once, with O(1) work each.

From Memoization: remembering answers

What does debouncing the search box do?Related

Answer: Waits for a short pause in typing (about 300 ms) and then sends one request

Debounce restarts a short timer on each key press, so only the final text is searched.

From Keeping the screen in sync

Why must promiseAll store results by index instead of push?Related

Answer: Promises finish in any order, but results must match input order

Push would record finishing order.

From Classic coding questions

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Functional patterns and testing” before its exam: Testing your code, Test doubles and testable design.
  2. Module 10 exam: Functional patterns and testing · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Performance and Node.js. 61 topics left in the course before the final exam.

← Previous: Composition, currying and pipelines