Tried to reimplement the debounce function today, pretty fun. The trick is to handle the timeoutId correctly, and cancel any previous invocations so only the last one remains.

 * Creates and returns a new debounced version of the passed function
 * which will postpone its execution until after wait milliseconds
 * have elapsed since the last time it was invoked.
export default function debounce(fn, time) {
  let timeoutId;

  const debouncedFn = (...args) => {
    if (timeoutId) {

    timeoutId = setTimeout(() => {
      fn.apply(this, args);
    }, time);

  debouncedFn.cancel = () => {

  return debouncedFn;

This implementation is not too dissimilar to what underscore does1, just with some extra options.

The Lodash debounce implementation2 is very hard to follow, maybe because its all in one file :shrug: