JavaScript Debounce Function

By  on  

One of the biggest mistakes I see when looking to optimize existing code is the absence of the debounce function.  If your web app uses JavaScript to accomplish taxing tasks, a debounce function is essential to ensuring a given task doesn't fire so often that it bricks browser performance.

For those of you who don't know what a debounce function does, it limits the rate at which a function can fire. A quick example:  you have a resize listener on the window which does some element dimension calculations and (possibly)  repositions a few elements.  That isn't a heavy task in itself but being repeatedly fired after numerous resizes will really slow your site down.  Why not limit the rate at which the function can fire?

Here's the basic JavaScript debounce function (as taken from Underscore.js):

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) func.apply(context, args);
		};
		var callNow = immediate && !timeout;
		clearTimeout(timeout);
		timeout = setTimeout(later, wait);
		if (callNow) func.apply(context, args);
	};
};

You'll pass the debounce function the function to execute and the fire rate limit in milliseconds.  Here's an example usage:

var myEfficientFn = debounce(function() {
	// All the taxing stuff you do
}, 250);

window.addEventListener('resize', myEfficientFn);

The function above will only fire once every quarter of a second instead of as quickly as it's triggered; an incredible performance boost in some cases.

I'm often asked what rate should be used when debouncing, and it's an impossible question to answer because it depends on the task.  The best way to know is testing different rates yourself and seeing where you notice a slowdown -- if you notice it, so will your users!

Recent Features

  • By
    Vibration API

    Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user.  One of those simple APIs the Vibration API.  The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...

  • By
    Introducing MooTools Templated

    One major problem with creating UI components with the MooTools JavaScript framework is that there isn't a great way of allowing customization of template and ease of node creation. As of today, there are two ways of creating: new Element Madness The first way to create UI-driven...

Incredible Demos

  • By
    MooTools Clipboard Plugin

    The ability to place content into a user's clipboard can be extremely convenient for the user. Instead of clicking and dragging down what could be a lengthy document, the user can copy the contents of a specific area by a single click of a mouse.

  • By
    Facebook Sliders With Mootools and CSS

    One of the great parts of being a developer that uses Facebook is that I can get some great ideas for progressive website enhancement. Facebook incorporates many advanced JavaScript and AJAX features: photo loads by left and right arrow, dropdown menus, modal windows, and...

Discussion

  1. Never heard of this technique and it makes completely sense! Your example of a resize event is probably the best example to understand the importance of such a technique!

    Thanks a lot for the tip, I’ll make sure to use when needed next time!

    • Ajay

      The other example is scroll (onScroll Event) which is often fired a lot of times on some website.

  2. the guys from Loop Infinito have an awesome article about debounce (and throttle), in portuguese:
    http://loopinfinito.com.br/2013/09/24/throttle-e-debounce-patterns-em-javascript/

  3. Hao

    Didn’t you write about this 2 years ago? http://davidwalsh.name/function-debounce

    • Yes, but (1) this one’s shorter and (2) people seem to have though you needed a JS toolkit to make it happen. I’m clarifying the minimal requirement here.

  4. Nice! Also worth mentioning the additional benefit of combining the debounce with requestAnimationFrame (IE10+)—there’s a nice write-up on usage and applications over at http://www.html5rocks.com/en/tutorials/speed/animations/.

  5. You can also use this to prevent client to overwhelm a web service.

    Ex: an instant search function. You don’t want a HTTP call whenever every single key is pressed, but you’ll want to wait the user to end typing (a few hundreds ms should be enough) before sending the HTTP request.

  6. Joakim Hedlund

    To clarify, this only fires myAwesomeFunc() once every 250ms,it doesn’t queue it up fifty times?

  7. With, that made little sense. Sorry about that!

    What I want to ask is whether the debounce simply queues up function calls or if it actually cancels any subsequent calls within the timeframe (i.e. 250ms).

    • Correct — any calls during the wait period are ignored.

  8. MaxArt

    I use to define a debounce method on Function.prototype for this. Very handy.

  9. Fran

    Just to clear this out:
    If myEfficientFn is called while waiting it will re start the timeout, so it will be possible to delay forever the execution of that function by calling it in periodic basis.

    Also, in this line:

    if (immediate && !timeout) func.apply(context, args);
    

    Why are you asking for !timeout ?
    Would this timeout be different to null every time unless wait is 0?

  10. It might be worth noting the difference between throttling and debouncing, as the terms are often confused. There is a subtle, but important, difference between the two.

    A debounced function is called only once in a given period, delay milliseconds after its last invocation (the timer is reset on every call). Whereas a throttled function is limited to be called no more than once every delay milliseconds.

    It’s easiest to understand with an example:

    function target() {
      console.log("called");
    }
    
    var delay = 250;
    
    // assume presence of `debounce` and `throttle` functions
    var debounced = debounce(target, delay);
    var throttled = throttle(target, delay);
    
    function repeatedlyCall(func) {
      var i = 0;
    
      var timer = setInterval(function() {
        if(i < 10) {
           clearInterval(timer);
        }
        func();
        i++;
      }, 100);
    }
    
    // call each function once every 100ms for 1s
    repeatedlyCall(debounced);
    repeatedlyCall(throttled);
    

    The throttled function will be called 4 times because throttling guarantees the callback will only be invoked at most once every delay milliseconds. However the debounced function will only be called a single time, because it resets its internal timer on every call, and so executes 250ms (the supplied delay) after the last call (1 second in)

    • hi, could you share/link to a code sample of the throttling function? i’m eager to compare it with article’s debounce().

    • Nick Williams

      Remy Sharp has good examples of both here: http://remysharp.com/2010/07/21/throttling-function-calls

      The difference between the two is slight, and which to use depends on your needs.

  11. I enjoyed reading this post. I’m a c#, ASP.NET, and C++ programmer with more than 3 years of experience in Web designig but not heard of this technique before.

  12. Ron Ritter

    Hi, what a great piece of coding, I am trying to use the flip so that it starts automatically with java when I open a page in an epub html, however I can’t seem to integrate the flip coding with javascript. I need to remove the hover effect and replace it with javascript function to run the html page on opening , can you help as I must be doing something basically wrong here,

    Tks Ron Ritter

  13. Ron Ritter

    <pre Ron Ritter February 5, 2014
    Hi, what a great piece of coding, I am trying to use the flip so that it starts automatically with java when I open a page in an epub html, however I can’t seem to integrate the flip coding with javascript. I need to remove the hover effect and replace it with javascript function to run the html page on opening , can you help as I must be doing something basically wrong here,

    Tks Ron Ritter

  14. Have been using this pattern for sometime without knowing that it is called “debounce” :)

    http://stackoverflow.com/a/21382048/87015

    You will also find this implemented in jQuery UI autocomplete widget.

  15. Loved the 160bytes minified version, very useful for projects without Underscore and Lodash!

  16. tomByrer

    Lo-Dash as a more complete/complex version also:
    http://lodash.com/docs#debounce

  17. This is helpful, as I often bind event handlers to window resize in order to handle some responsive layout on resizing (like, sticky elements). Most of the times the resizing tends to be slow, mainly because of overcalling the event handler.

    this debounce function comes in handy to fix that.

  18. dve

    You should also consider creating a version of debounce that utilizes requestAnimationFrame() (if available) for even better performance.

  19. Dan

    The reason it’s called “debounce” is because it comes from a physical situation which literally has things bouncing:
    http://en.wikipedia.org/wiki/Switch#Contact_bounce

  20. Fabrizio

    How do you handle the scope of the function that is being debounced?

  21. Tom

    Was having some issues getting debounce to initialize an immediate function call.

    function debounce(func, wait, immediate) {
    	var timeout;
    	return function() {
    		var context = this, args = arguments;
    		clearTimeout(timeout);
    		//Moving this line above timeout assignment
    		if (immediate && !timeout) func.apply(context, args);
    		timeout = setTimeout(function() {
    			timeout = null;
    			if (!immediate) func.apply(context, args);
    		}, wait);
    	};
    };
    
    • Adam P

      Your code finally makes sense. I couldn’t see the logic of setting up a timeout and checking it’s value right after it.

    • Chris Adams

      I came here to report the same bug which Tom mentioned – the older version at http://davidwalsh.name/function-debounce uses a temporary variable so it can do the immediate && !timeout check before calling setTimeout.

  22. I’ve updated the post to fix said issue. Thanks everyone!

  23. I’ve seen so many websites goes completely weird while not using debounce technique, especially some of them that involved animations.

  24. Martijn

    Only the semicolon at the end isn’t neccesary ;)

  25. Jex Toth

    Isn’t there a huge problem with this debounce? -Multiple events

    Say event1 calls debounce with func1,
    then event2 comes along and calls debounce with func2. (before func1 is fired).

    The events are different so both functions should fire, won’t debounce will only fire func2?

  26. Jex Toth

    ^nevermind :) I was wrong.. Tried to use as a self-invoking function, that’s why it didn’t work properly for me

  27. Nice technique, mr. Walsh. I’m going to see whether this reduces the initialisation lag in one of my word games.

  28. Zero Liu

    Why do you need immediate param? What’s the difference between setting wait to 0 and assigning true to immediate?

    • Exactly my point. The immediate is of no use, specially if the implementation above returns a function.

      If the debounce method is changed to immediately exec, instead of returning a value, the immediate param can be put to use.

  29. It’s not clear from your code how one would use immediate
    If immediate is passed right at the debounce call, then what’s the use of a debounce function.

    Either you are missing an IIFE (Immediately invoked function expression call) or the intention of your code is unclear.

    Since your code returns a function, the returned function should have the ability to immediately execute if immediate is passed true. I don’t see this feature

    This looks like you are storing the return value of the debounce function in some other variable.
    for eg. (using your impl.)

    function sayHi () { ... }
    var sayHiDebounced = debounce(sayHi, 1000, true); // what's the use of this true if your intention is to debounce? this will always immediately exec.
    
    // Ideally, the debouncing should be as follows.
    sayHiDebounced(); // creates a timeout and wait
    sayHiDebounced(); // clears previous timeout, creates a timeout and wait
    sayHiDebounced(); // clears previous timeout, creates a timeout and wait
    ...
    ...
    sayHiDebounced(true); // ignores timeouts and executes sayHi immediately
    
  30. Smamatti

    I don’t understand why there is a variable arguments in line #8. Where is that defined?

    var context = this, args = arguments;
    • Smamatti

      Nevermind. I found out that there is an arguments object for functions. JSLint was complaining about this not being defined.

  31. Jacob

    Hello, I’m still having a tough time grokking what’s really going on here, as I’m not really proficient in JavaScript so much as I am competent. For a young developer like myself, would you be willing to do a line-by-line breakdown of what’s going on? I’m usually pretty quick on the uptake, but could really use a hand here. Thanks :)

  32. Thanks a lot for this tip! Helped me fix the slow performance on one of my apps.

  33. Sarbbottam Bandyopadhyay

    Wondering if it can be written as below mentioned.

    function debounce(func, wait, immediate) {
        var timeout;
        return function() {
          var context = this;
          var args = arguments;
          var later = function() {
            func.apply(context, args);
          };
          
          if (immediate) {
            func.apply(context, args);
          } else {
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);  
          }
        };
    };
    
    • reyuto

      for immediate=true, the second call would execute immediately, without debouncing.

  34. it not run with jquery-2.1.1.min.js???
    i have just test it in 2.1.1 . it dont run.
    please check http://sieuthimayphatdien.com.vn/may-phat-dien

  35. Łukasz

    Can later function be moved outside returned function scope, like this:

    function debounce(func, wait, immediate) {
        var timeout;
        var context = this;
        var args = arguments;
    
        var later = function () {
          timeout = null;
          if (!immediate) {
            func.apply(context, args);
          }
        };
    
        return function () {
    
          var callNow = immediate && !timeout;
    
          clearTimeout(timeout);
          timeout = setTimeout(later, wait);
          if (callNow) {
            func.apply(context, args);
          }
        };
    
      };
    
  36. Guillaume

    It worked but I had to move timeout to the parent scope (global in my case) to make it work.
    Indeed I haven’t attach debounce to an event but to a return event of jQuery Tooltip content() method.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!