If you code in JavaScript, you should know all about debouncing functions.

When an event listener such as a window ‘resize’ fires, sometimes it will fire 72,000 times per second. That means your callback function is also firing 72,000 times in a row, bogging down the browser.

This isn’t as much of a problem as it used to be, but debouncing is still needed and should still be used.

So what’s the best way to debounce/throttle in JavaScript?

Debounce vs Throttle

Debouncing comes from hardware engineering. When you press a physical button, two conductive surfaces generally come into contact, completing a circuit.

Mechanically, the switch doesn’t just go from ‘off’ to ‘on’ in an instant. Two conductive surfaces tend to bounce, like so:

Switch DebounceRight. So, we need a ‘debounce circuit’, which filters the bouncing and makes the switch (electrically) go from 0V (off) to 5V (on) in a binary fashion. Debounce functions in code perform the same service.

So what’s the difference between debouncing and throttling? A throttling function makes sure 1 trigger of your callback function happens during the time period X. So, if X = 1000ms, then your callback will only be called once per 1000ms, not once every 2, 50, or 100ms – as the case may be.

A debounce function makes sure that either:

  1. Your callback doesn’t happen until at least time period X has elapsed (i.e. if debounce is 200ms, your callback won’t run 50ms and 125ms later if the button is pushed/bounces again. It will only happen after 200ms of peace and quiet)
  2. Your callback happens immediately, but then not again until at least time period X has elapsed

So, debounce vs throttle (which is a hot topic of conversation) is really kind of a matter of perspective. Both are more or less the same thing – in most use cases. In both, the point is to take Too Many Callback Triggers and reduce it to only one or a few callback triggers in time period X. DONE!

The Simplest and Best Debounce

If you look this up, you will find one million and two different implementations. Some of them are downright scary in their complexity.

There’s really only one implementation you need, which I include in my PikaJS library as $.debounce():

$.debounce = function(func, delay, now) {
  var timeout;
  now = now || false;
  return function() {
    var obj = this, args = arguments;
    function delayed() {
      if (!now) { func.apply(obj, args); }
      timeout = null;
    }
    if (timeout) {
      clearTimeout(timeout);
    } else if (now) {
      func.apply(obj, args);
    }
    timeout = setTimeout(delayed, delay || 100); 
  }
}

As to how this function works, you’ll understand if you stare at it long enough. The key is that the var timeout persists across multiple calls to $.debounce because $.debounce returns a function, and that function includes a reference to timeout. That’s the secret! Redeclaring ‘var timeout;‘ in another call to $.debounce does NOT set timeout to ‘undefined’ if it already exists. Try it in your browser’s console!

Okeydokey, so next up: some examples of how to use it…

  • func = your callback function
  • delay = the time period X, in ms, which defaults to 100ms
  • now = debounce with immediate execution, then wait (now = true), or callback execution happens after X ms (now = false, the default)

Okay, so let’s use it!

// Only fire resize() 100ms after the window resizing events have stopped
function resize() {
  console.log("window resize!");
}
$(window).on('resize', $.debounce(resize));

Due to variable scope and closures and that kind of jazz, this will NOT WORK:

// This WILL NOT BE DEBOUNCED:
$(window).on('resize', function() {
    // This func will be called once immediately:
    $.debounce(MyFunction(stuff), 50, false);
    if (something) {
      // This func will be called many times, but not debounced:
      $.debounce(OtherStuff, 50, false);
    }
  }, 50, false)
);

You have to do it like this:

// Call multiple functions 50ms after the window resizing events have stopped
$(window).on('resize', $.debounce(
  function() {
    MyFunction(stuff);
    if (something) { OtherStuff(); }
  }, 50, false)
);

If you just wanted to call a single callback function with one or more arguments, you must do this:

// This will work nicely:
$(window).on('resize', $.debounce(function() { MyFunction(stuff); }, 50, false));

The trick is that the event listener MUST call $.debounce() directly, and repeatedly. If you include the debounce function inside of another function(), then the timeout variable is not carried over between successive calls to $.debounce.

Short version: it’s a variable scope/closure issue, so do it like I explain above and you’ll be happy!

So, this is your general-purpose, always-works code:

// General Purpose:
$(ELEMENT).on('EVENT_NAME', $.debounce(
  function(evt) {
    // Do stuff here
    // `evt` contains EVENT_NAME's Event data!
    // Call functions with no arguments here
    // Call functions with 1 million arguments here
    // Do literally anything you want here
  }, 100, false)
);

Note that ‘arguments‘ in $.debounce() is for passing along the EVENT_NAME‘s Event – i.e. the resize event in your handler. It’s NOT for passing along arguments to your callback func!! That’s another area where coders misuse their debounce function.

Great, but I heard requestAnimationFrame is better!

Yes, but no.

Increasingly, I’m seeing JS libraries and StackOverflow posts where people are recommending using requestAnimationFrame (rAF) to debounce JS events. This is a bad idea for a few reasons.

First, it increases the complexity of the debounce function considerably. If rAF is available, it uses it. If not, it defaults back to setTimeout.

Second, rAF is intended to do something when the browser is about to redraw the screen, more or less. But it also kind of tells the browser that YOU want to redraw the screen. It’s intended for things like animations – not debouncing a window resize or a button click!

Third, using rAF severely violates the principle of KISS: Keep It Simple, Stupid. If a few lines of code will do the trick in 99.9% of cases, then why make the code 4 times larger and 8 times more complicated? So you can claim this is the “modern way” of doing things? HA!

A large part of the reason that our “modern” systems of code suck so badly is exactly because coders don’t think about what they’re doing. Instead, we all just want a ‘module’ to do everything for us – no questions asked.

So yeah, do yourself a favor: use a simple, tiny, setTimeout-based debounce function, and be happy.

PikaJS rulez!

Easiest way to do that? Grab PikaJS and get crackin’!!

 

 

Need help? Hire me!
Get Scottie Stuff!