# How to write your own debounce function

I suppose you already know why we use `debounce`, but just as a little refresher, here's my own definition: *"we use* `debounce` *when we do not want to fire a function as soon as it gets called, but instead, we want to handle the cases when the function gets called multiple times in a short timeframe and have our program run only once”*.

If you do not like my definition [here's the MDN one](https://developer.mozilla.org/en-US/docs/Glossary/Debounce), it's not long, and I advise you to read it.

This article focuses on how to write such a function yourself and why it works the way it does.

First and foremost, `debounce` is a function that returns another function, the *debounced* version of the function we pass as a first argument. We define this function to tell our application that we need a new one that does the same things as the original, like reading/writing to our database or performing other complex operations. However, we want to fire it only when the `wait` time has passed since our last call.

Let's make things real: imagine Google's autosuggesting feature. While it seems to suggest keywords as we time, the engineers at Google cannot afford to query their immense database for every keystroke. Instead, they `debounce` the keyword search that's read from the `input` so the app can wait for the user to stop writing and collect all the characters the user has been able to type to make a better guess (suggestion) about what the user is looking for.

Now that you have this image in your mind, let's start from the function signature.

```ts
function debounce(func: Function, wait: number): Function {
	// function body
}
```

Other implementations of `debounce` accept even more attributes, but I want to keep it simple, as this will allow us to pass the first challenge of the [GFE 75 collection of GreatFrontEnd](https://www.greatfrontend.com/interviews/gfe75).

Just to be clear, I am not here to give you the golden ticket that will allow you to pass these challenges; I want you to earn it while understanding the ins and outs of the challenges that I face while completing it on my own.

With that said, as you can see, we just pass two arguments to our `debounce` function: the `func` that is the function that we want to debounce, and a `wait` value that will be the number of milliseconds that we will wait after the last subsequent calls of `func` gets invoked.

But how does it work?

If you started to think about `setTimeout` as soon as you read *"we do not want to fire as soon as it gets called"*, you were right. We **need** to use this built in method in order to instruct our program about this functionality.

But remember, we also have to clear the timeout if the same function gets called multiple times in the timeframe defined by `wait`.

That is because while `setTimeout` is a powerful function, it is not able to handle the clearing by itself. We need to tell it to clear the timeout of the last running function before to fire a new one.

But how do we keep track of the latest timer set?

And here's where another important concept of JavaScript programming comes into the scene: **closures**.

I will write more about this kind of function in a separate article. It is important to know now that with closure, you can reference a value generated by a function even when it has terminated its execution.

And that's what does the trick.

### Clearing the timeout

With this knowledge, we know that the body of our `debounce` function looks like this:

```ts
function debounce(func: Function, wait: number): Function {
  // Keep track of the timeout ID
  let timeoutId: number;

  return function(this: any, ...args: any[]){
	// Clear previous timeout by accessing the outer timeout ID
    clearTimeout(timeoutId)

	// Set a new timeout with our function
    timeoutId = setTimeout(
	    // The function that will be executed after wait
    , wait)
  }
}
```

The function that we return from `debounce` is the closure we were talking about. It is able to access the `timeoutId` and clear the scheduled execution of the function in case we have a subsequent call.

Now that we now how to handle the timeout we set for the execution, it's time to properly talk about how we will execute the function itself.

### The proper way to call the `func`

Knowing the `setTimeout` signature, my first attempt has been the following:

```typescript
function debounce2(func: Function, wait: number): Function {
  let timerId: number;

  return function(this: any, ...args: any[]){
    clearTimeout(timerId)

    timerId = setTimeout(func, wait, ...args)
  }
}
```

As you can see, I leveraged `setTimeout`'s ability to pass additional arguments to the delayed `func` by spreading the array we collect during initialization.

Doing so allowed me to pass almost all the tests that GFE has set for this challenge. There was only one test that I was failing, the one that allowed the callback to access the `this` context.

That's because most of the time we just use a simple arrow function inside `setTimeout`, and doing so means that we do not care about `this` binding as arrow functions do not have their own `this`.

But the guys over GFE are smart and want you to prepare for the unexpected, that's why they put the following test:

```ts
test('callbacks can access `this`', (done) => {
  const increment = debounce(function (this: any, delta: number) {
    this.val += delta;
  }, 10);

  const obj = {
    val: 2,
    increment,
  };

  expect(obj.val).toBe(2);
  obj.increment(3);
  expect(obj.val).toBe(2);

  setTimeout(() => {
    expect(obj.val).toBe(5);
    done();
  }, 20);
});
```

As you can see, in this test, we attach the debounced `increment` function as a method of our `obj`, and we expect to be able to access `this.val` and increase it.

While my previous definition had other fallbacks, like the inability to do more besides calling `func` (like a simple `console.log`, for example), the biggest one is that we could not bind `this` to it.

While there are several approaches to solve this issue, I decided to go with the `apply` direction, rewriting the function call like so:

```ts
export default function debounce(func: Function, wait: number): Function {
  let timerId: number;

  return function(this: any, ...args: any[]){
    clearTimeout(timerId)

    timerId = setTimeout(() => {func.apply(this, args)}, wait)
  }
}
```

This version works well for all tests because, in many cases, `this` will result in `undefined` because it will refer to the `this` value of the debounced function. Since, in almost all tests, the debounced function is just an arrow function that has no knowledge of `this` our `apply` call will just use the `args` we pass to the function.

Like in the following test:

```ts
test('uses arguments of latest invocation', (done) => {
    let i = 21;
    
    const increment = debounce((a: number, b: number) => {
        i += a * b;
    }, 10);

    expect(i).toBe(21);
    increment(3, 7);
    increment(4, 5);
    expect(i).toBe(21);

    setTimeout(() => {
	        expect(i).toBe(41);
	        done();
        }, 20);
    });
});
```

Here, `increment()` gets called without anything chained on the left side, and since we're talking about an arrow function, we can safely ignore the `this` value and collect all the `args` safely.

Instead, in the previous failing test, we defined the function that we want to `debounce` with the standard declaration, and after adding it as a method of `obj`, the function was able to access to `this` that was represented by the object it was connected to, the `obj` object to be clear.

### Suggestions and conclusion

I hope that this article helped you better understand what happens when you use one of the many `debounce` functions found in utility libraries like Lodash.

If you want to go the extra mile, you could integrate other `options` like the ones defined in the [Lodash documentation](https://lodash.com/docs/4.17.15#debounce) into your `debounce`.

I prefer to close this article here, though, as I reached my scope in explaining to you how - but especially why - you could solve the first challenge of the GFE 75 path.

If you want to know more, start following me. I'll keep writing deep-dive articles like the one you are reading about the challenges proposed by the amazing GreatFrontEnd platform.
