Underscore debounce vs vanilla Javascript setTimeout

setTimeout and debounce are in no way the same thing. setTimeout simply waits n milliseconds and the invokes the supplied function. debounce on the other hand returns a function that only calls the callback after n milliseconds after the last time the functions was called.

Huge difference. Debouncing/throttling (they are not the same thing) functions are often used to reduced the amount of function calls as a result of user input. Imagine a autocomplete/typeahead field. You might do an ajax request every keystroke, but that can get kind of heavy, so instead you can debounce the function, so it will only fire 200ms after the last keystroke.

You can read up on the documentation here: https://lodash.com/docs#debounce


They are very different and used in completely different cases.

  1. _.debounce returns a function, setTimeout returns an id which you can use to cancel the timeOut.

  2. No matter how many times you call the function which is returned by _.debounce, it will run only once in the given time frame.

var log_once = _.debounce(log, 5000);

function log() {
  console.log('prints');
}

log_once();
log_once();
log_once();
log_once();
log_once();

var id = setTimeout(function() {
  console.log('hello');
}, 3000);
clearTimeout(id);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>

You can also implement your own debounce in vanilla JavaScript. A widely quoted article is David Walsh's article on function debouncing with underscore which includes the source code used by underscore in their implementation:

// 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);
    };
};

The debounce function serves as a generator for the actual function you'd like to call, that way the state can be persisted inside of the closure like this:

// example function
let sayHello = (name) => console.log(`Hi ${name}`)

// generate a debounced version with a min time between calls of 2 seconds
let sayHelloDebounced = debounce(sayHello, 2000)

// call however you want
sayHelloDebounced('David')

Demo in Stack Snippets

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);
	};
};

let sayHello = (name) => console.log(`Hi ${name}`)

let sayHelloDebounced = debounce(sayHello, 2000)

sayHelloDebounced('David')
sayHelloDebounced('David')
sayHelloDebounced('David')

Other Implementations

  • Underscore Docs | Underscore Source
  • Lodash Docs | Lodash Source
  • 30 Seconds of Code