# Debounce and Throttle

# Introduction

I'm writing this post again because I had some difficulties in explaining the difference between debounce and throttle.

In human words:

Debounce -&gt; "Wait until X ms have passed since the last call, then call the function".

Throttle -&gt; "Call the function at most once every X ms".

They sound super similar, but they are not the same. Let's also implement both of them in TypeScript.

# Debounce

A common use case for debounce is search. You search for a query, but don't want to make a request to the server on every keystroke. Let the user finish typing and then make a request in about 300ms.

```js
function debounce(func: Function, delay: number) {
  let timeoutId: NodeJS.Timeout | null = null;

  return function (this: any, ...args: any[]) {
    const context = this;
    if (timeoutId) clearTimeout(timeoutId);

    timeoutId = setTimeout(function () {
      func.apply(context, args);
    }, delay);
  };
}
```

Before I explain the code, you would use it like this in practice:

```js
function search(query: string) {
  console.log("Searching for", query);
}

const debouncedSearch = debounce(search, 300);

debouncedSearch("react");
debouncedSearch("redux");
debouncedSearch("react router");
debouncedSearch("typescript");
```

Spoiler: `typescript` will be the only one that will be called (last `debouncedSearch` call).

Going back to the code, the debounce function accepts a function and a delay. In return, it gives you a new function that whenever called, will delay the execution of the original function by the given delay.

The confusing part is likely this bit:

```js
return function (this: any, ...args: any[]) {
  const context = this;
  if (timeoutId) clearTimeout(timeoutId);

  timeoutId = setTimeout(function () {
    func.apply(context, args);
  }, delay);
};
```

Let's start with the parameters. `this` in the parameter is a TypeScript feature that tells you the function is a method of an object. This isn't needed to be fair, but it's a good practice to include it. The reason I say it's not needed is because you can have anything in there. You can always do `const context = this` in JavaScript.

The second parameter is the arguments. When you do `...args`, you're telling TypeScript that the function can take any number of arguments and group them in an array.

To be explicit in our code, we store the `this` context in a variable called `context`.

`setTimeout` returns an ID. If you want to cancel the timeout, you need to use that ID. Imagine you call debouncedSearch multiple times. You want to cancel the previous calls. That's why we check if `timeoutId` exists and we clear it to start fresh.

Lastly, this bit `func.apply(context, args);` is what actually calls the function. Here we call the original function with not just the correct arguments, but also the correct `this` context. This isn't always needed, but if we were given a method of an object where the method uses `this`, we need to make sure we pass the correct `this` context. In our search example, we wouldn't need it (we’re just making a request to the server).

## Closure

The pattern of returning a function from another function is called a closure. It's powerful because every time you call the debounce function with different methods, each function that gets returned has its own `timeoutid` variable.

```js
function debounce(func: Function, delay: number) {
  let timeoutId: NodeJS.Timeout | null = null;

  // Returned function will have access to `timeoutId`
  return function (this: any, ...args: any[]) {};
}
```

`timeoutId` is "closed over" by the returned function. That means that the returned function has access to the `timeoutId` variable.

Generally, closures is a common pattern where you need to manage "state" across multiple function calls.

# Throttle

Throttle is different from debounce. Instead of clearing the timeout and letting the last function be the debounced run, throttle will run the function at the beginning and then again every X ms.

Throttle is like saying "Give me the amount of time I should not let anything happen. When called the first time, I run right away. When called the second time, if the time you gave me is not up, I do nothing."

Now, throttle could say two things:

* "If you still call me during the cooldown time, I'll queue the last call you made."
    
* "If you still call me during the cooldown time, I'll just ignore it."
    

Whether you want the first or second behavior is up to you. First one is quite common though to not miss the last event.

Simple implementation where we ignore the last call during the cooldown time:

```js
function simpleThrottle<T extends Function>(func: T, limitMs: number) {
    let lastRun = 0;

    return function(this: any, ...args: any[]) {
        const now = Date.now();

        const timeElapsed = now - lastRun;

        if (timeElapsed >= limitMs) {
            lastRun = now;
            func.apply(this, args);
        }
    }
}

const handleEvent = simpleThrottle(eventHandler, 300);
```

Here, `eventHandler` is the function that will be called. `limitMs` is the amount of time we should not let anything happen.

We can check the time since the last call by using [`Date.now`](http://Date.now)`()`. `lastRun` is the timestamp of the last call. First time this is never an issue because then `now` is `timeElapsed` (since `lastRun` starts as 0). Which is obviously much greater than `limitMs`.

Let's look at an example where we queue the last call:

```js
function queuedThrottle<T extends Function>(func: T, limitMs: number) {
    let lastRun = 0;
    let timeoutId: NodeJS.Timeout | null = null;

    return function(this: any, ...args: any[]) {
        const now = Date.now();
        const context = this;

        const timeElapsed = now - lastRun;

        // Oh no, we're still in cooldown.
        if (timeElapsed < limitMs) {
            // Was something else queued?
            // Because we were called again, let's clear it.
            // We have a newer function to queue
            if (timeoutId) {
                clearTimeout(timeoutId);
            }

            // Example
            // limitMs = 300
            // timeElapsed = 200
            // timeRemaining = 300 - 200 = 100
            const timeRemaining = limitMs - timeElapsed;

            // Queue the new function
            // We want this to run after the cooldown time has passed
            // We know how much time is left by checking `timeRemaining`
            timeoutId = setTimeout(() => {
                lastRun = Date.now();
                func.apply(context, args);
            }, timeRemaining);

            return;
        }

        // If we're not in cooldown, execute immediately
        lastRun = now;
        func.apply(context, args);
    }
}
```
