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
    Welcome to My New Office

    My first professional web development was at a small print shop where I sat in a windowless cubical all day. I suffered that boxed in environment for almost five years before I was able to find a remote job where I worked from home. The first...

  • By
    Convert XML to JSON with JavaScript

    If you follow me on Twitter, you know that I've been working on a super top secret mobile application using Appcelerator Titanium.  The experience has been great:  using JavaScript to create easy to write, easy to test, native mobile apps has been fun.  My...

Incredible Demos

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.

    • This is an amazing vanilla explanation and very much appreciated. Transformed my multi-line approach with a setTimeout filtering the last fire – to a logical place. √√√

  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.

    • Niall Campbell

      Throttled can be simply achieved by changing the way the timeout is cleared/set. eg..

      // Returns a function, that, as long as it continues to be invoked, will only
      // trigger every N milliseconds. If immediate is passed, trigger the 
      // function on the leading edge, instead of the trailing.
      function throttle(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;
      		if ( !timeout ) timeout = setTimeout( later, wait );
      		if (callNow) func.apply(context, args);
      	};
      };
      

      Note that the timeout is not destroyed and recreated for every call (unlike debouncing). Its destroyed after the timeout has completed and created on the first call after its been destroyed.

  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
    
    • Logan

      By using immediate, you execute the function at the beginning of the interval rather than after. You would likely use it in conjunction with an event that fires very often where you may want to limit the calls to your function for performance (e.g. scroll, resize). The immediate parameter gives you flexibility over whether you want to execute before setting the timer, or after it completes.

      e.g.
      On scroll handler with immediate + 500ms wait
      *user begins scrolling* – *execute fn* – wait 500ms – *execute fn*
      vs on scroll handler with no immediate + 500ms wait
      *user begins scrolling* – wait 500ms – *execute fn* – wait 500ms

  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);
          }
        };
    
      };
    
    • Niall Campbell

      If the later function is moved outside the returned function scope, no arguments passed to the returned function will be available to it.

        my_mousemove_callback = function( event ) {
          console.log( event );
        };
        var debounced_mousemove_callback = debounce( my_mousemove_callback, 100 );
      
        window.addEventListener( 'mousemove', debounced_mousemove_callback );
      
  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.

  37. Jorge

    I still dont get when is useful this function, can someone clarify when will be a good idea use this code? I made a simple resize function, and using or not debounce is not a big difference…

    https://jsbin.com/jewefacoce/edit?js,console,output

  38. Brilliant, the more I read on your blog, the less I am dependent on jquery.

  39. Göran

    1) How can you do

    var callNow = immediate && !timeout;
    clearTimeout(timeout);

    at a location where the value of the variable timeout may be undefined? Please explain any assumptions and prerequisites for using the provided debounce function.

    2) In the provided example of how to use the debounce function, that function is called with two arguments (a function and a duration). But the debounce function is defined to take three parameters, not two. Please explain why there is a difference in the number of arguments.

  40. Mikael Boutin

    Which book must i read to know some of these powerful tricks? Or what should i look for? Thank you David!

  41. Rajesh Kurve

    Really great tip to boost client side performance and site lag issues. Thanks!

  42. Michal

    I am wondering whether it matters that you assign timeout = null in the later function. Why not undefined?

  43. No One

    I think the description of the function in your article ought to be changed. Your code resembles a debounce, but the text suggest a throttle.

    “The function above will only fire once every quarter of a second instead of as quickly as it’s triggered”

    That’s not really what happens. The function in your example will never be executed as long as the resize event handler fires faster than 250 ms, but rather 250 ms after you stop resizing.

  44. Julie

    Hi – My background is actually in Ad Ops, not web development. But, I’m working on a site this has debouncing in its googletags. It makes the ads resize if the browser width narrows, but it also appears to be making multiple ad requests per ad slot. Does that make any sense?

  45. Yeah I’m not sure this is debounce either. It has strange behaviour like if I hold down a key it doesn’t spit one event out every n ms, even if I’m still holding down the key after the debounce duration (my understanding of debounce).

    Also the default to non-immediate is a bit sketchy for input (the main thing I’d use a debounce for).

    What was the original use-case?

  46. ignore that last comment, looks like I’m coming at this from a skewed angle.

  47. xelra

    There’s a lot going on in this function. Context and args and apply().

    When searching for “js debounce” this is the top entry. It would be really great if you could do a line by line breakdown and explanation for dummies.

  48. Alex

    Hi,
    I hope you can reply me. I have a question.. I need to do a parallax effect in 50 element.. For doing it, Im using the $(window).scroll(function())

    If an element is visible in the screen section in the stage, I active a specific setInterval who will be destroied if the element is too up or too down. I’m using setInterval because the parallaxed image have a friction movement

    Is this solution correct or it require too client resource? Is better the debounce method?

    $(window).scroll(function(){
      scroll = $(window).scrollTop(); // qta scroll
      screenHeight  = $(window).height(); // altezza browser
      $(".field-image").each(function(){
        topScreen =  Math.round( scroll - height + 20); // il 20 è per dare una marginalità
        bottomScreen = Math.round( scroll + screenHeight );
        if (!$(this).attr('id') ) {
          topPosition = Math.round( $(this).offset().top ); // distanza dal top
          $(this).attr('id', "field" + topPosition);
        } else {
          topPosition = $(this).attr('id').replace("field", "");
        }
        if (topPosition > topScreen && topPosition < bottomScreen) {
          if(typeof window["interval" + topPosition] === 'undefined' || window["interval" + topPosition] == null) {
    
            window["interval" + topPosition] = setInterval(function(elem){
              elemTop=  Math.round( $(elem).offset().top );
              img = elem.find("img");
              thisPos = parseInt(img.css("margin-top").replace("px", ""));
              [...]
            }, 30, $(this));
          }
        } else {
          if(typeof window["interval" + topPosition] !== 'undefined' && window["interval" + topPosition] != null) {
            clearInterval (window["interval" + topPosition]);
            window["interval" + topPosition] = null;
          }
        }
      });
    });
    
    
  49. Hadrien

    Mind blown.

  50. Brian Bellino

    You rock man, I just used this code in 2018!

  51. Sam

    I was so confused by this because you are clearing the timeout on every function call but that doesn’t match your description for what this function does. Luckily, a little searching led me to find out what was going on here.

    “For those of you who don’t know what a debounce function does, it limits the rate at which a function can fire… Why not limit the rate at which the function can fire?”

    This is what a throttling function would do. A debounce function completely halts function calls until the call rate of the function falls low enough. That’s a different thing from “limiting the rate at which a function can fire”.

    “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.”

    This is an inaccurate/misleading explanation. With the code you provided, the function will only fire a single time while the trigger rate is above 4/second, not fire every .25 seconds while being triggered. If someone is non-stop triggering the function faster than 4 times per second, the function will only fire a single time at either the start of end of the rapid triggering depending on what immediate value is passed (because the timeout is being cleared on every function call). You’re showing the code for a debounce function but describing it as if it’s a throttling function.

    • Amit Bansal

      I wanted to put the same comment here and saw that you already have. Whats interesting is that the comment/description with the original piece of code from underscore actually has the right & fantastic explanation of debounce, and nothing more needs to be added to it.

  52. Nick

    I came up with a similar function below that combines debouncing with throttling. The registered callback function also receives information about which type of debounce/throttle call it is receiving, which allows the callback to do things such as apply a class or style at the ‘start’ (e.g. “overflow: hidden” so scrollbars don’t appear during a resize), do some minimal UI updates ‘during’, and then finalize changes at the ‘end’. Both debouncing and throttling are optional (e.g. if you just want debouncing, pass 0 for throttlewait).

    function debounceWithThrottle(func, debouncewait, throttlewait, callatstart, callatend) {
    	var debouncetimeout, throttletimeout;
    
    	return function() {
    		var context = this, args = arguments;
    
    		if (callatstart && !debouncetimeout)  func.apply(context, [].concat(args, 'start'));
    
    		if (!throttletimeout && throttlewait > 0)
    		{
    			throttletimeout = setInterval(function() {
    				func.apply(context, [].concat(args, 'during'));
    			}, throttlewait);
    		}
    
    		clearTimeout(debouncetimeout);
    		debouncetimeout = setTimeout(function() {
    			clearTimeout(throttletimeout);
    			throttletimeout = null;
    			debouncetimeout = null;
    
    			if (callatend)  func.apply(context, [].concat(args, 'end'));
    		}, debouncewait);
    	};
    };
    
    // Example usage below demonstrates the difference between debouncing w/ throttling vs. the raw call count.  The values below eliminate approximately 85% of the mousemove calls.
    var numcalls = 0, numcalls2 = 0;
    var myfunc = debounceWithThrottle(function(e, dtmode) {
    	numcalls++;
    
    	console.log(Date.now() + ' - ' + dtmode + ' - ' + numcalls + ' debounced/throttled' + ', ' + numcalls2 + ' total');
    }, 50, 250, true, true);
    
    var myfunc2 = function(e) {
    	numcalls2++;
    };
    
    window.addEventListener('mousemove', myfunc);
    window.addEventListener('mousemove', myfunc2);
    

    Note that certain UI updates (e.g. resizing and repositioning DOM elements) in a setTimeout/setInterval callback causes problems in some browsers (e.g. Chrome for Android) where the browser will halt execution of that setTimeout/setInterval until the browser decides enough time has passed – the delay before resuming is approximately 3 seconds even if you are being a responsible citizen using a debouncer function. If you use the function above, your callback should call window.requestAnimationFrame() with a function that makes the actual UI change(s) so that the setTimeout/setInterval calls of the debouncer/throttler continue to operate unhindered.

  53. IIFE version for inline functions

    window.addEventListener('scroll', debounce( () => {
          // All the taxing stuff you do
        }, 250)
    );
    
  54. Mathew D Ruszczycky

    Improved.

    const debounce = (func, wait, immediate) => {
    	let timeout;
    
    	return function() {
    		let context = this;
    		let args = arguments;
    
    		clearTimeout(timeout);
    
    		timeout = setTimeout(function() {
    			timeout = null;
    
    			if (!immediate) {
    				func.apply(context, args)
    			}
    		}, wait);
    
    		if (immediate && !timeout) {
    			func.apply(context, args)
    		}
    	};
    };
    
    • Eugene

      Your improved version not working with immediate option. Fixed

      function debounceExtended(func, wait, immediate) {
        let timeout;
      
        return function() {
          let context = this;
          let args = arguments;
      
          if (immediate && !timeout) {
              func.apply(context, args);
          }
      
          clearTimeout(timeout);
      
          timeout = setTimeout(function() {
            timeout = null;
      
            if (!immediate) {
              func.apply(context, args);
            }
          }, wait);
        };
      }
      
  55. Aditya Naik

    why we need to set timeout = null; inside setTimeout

  56. Monaye

    Here is another one with ES6 from
    https://dev.to/monaye/refactor-davidwalsh-s-debounce-function-5afc

    const debounce = (func, delay, immediate) => {
      let timerId;
      return (...args) => {
        const boundFunc = func.bind(this, ...args);
        clearTimeout(timerId);
        if (immediate && !timerId) {
          boundFunc();
        }
        const calleeFunc = immediate ? () => { timerId = null } : boundFunc;
        timerId = setTimeout(calleeFunc, delay);
      }
    }
    
  57. Thank you, David, I found this very useful. I wanted to give feedback from my experience using your snippet. I actually found it more readable to have debounce call a named function inside the addEventListener declaration:

    window.addEventListener('resize', debounce(myEfficientFn, 250));
    
    function myEfficientFn() {
    	// All the taxing stuff you do
    }
    

    Half a dozen, six of another, but I like having the bulky function declared after the event binding which you can’t do with a variable/function expression.

    Thanks again, this is great!

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