Implementing Throttle in JavaScript



What is Throttle and Why is it Important?

Throttle is a technique used to limit the rate at which a function can execute. Unlike debounce, which delays the execution until after a certain period has passed without the function being called, throttle ensures that the function is called at most once in a specified period. This is especially useful for scenarios where you want to ensure a function executes at regular intervals, regardless of how many times it is triggered.

Real Interview Insights

Interviewers often test your understanding of throttle by asking you to:

  • Implement a throttle function from scratch.
  • Explain scenarios where throttling is beneficial.
  • Optimize existing code using throttle.

Implementing Throttle

Let's start with a basic implementation of the throttle function in JavaScript:

function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    const context = this;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}
Explanation:
  • Throttle State: Use a flag (inThrottle) to control whether the function should be called.
  • Timeout Management: Use setTimeout to reset the throttle state after the specified limit.

Practical Example

Consider a scenario where you want to throttle the window resize event to prevent excessive function calls:

function handleResize() {
  console.log('Window resized');
}
 
const throttledResize = throttle(handleResize, 200);
 
window.addEventListener('resize', throttledResize);

In this example:

  • The handleResize function is called at most once every 200 milliseconds, regardless of how many times the resize event is fired.

Advanced Throttle: Leading and Trailing Edge Execution

Sometimes, you might want more control over when the throttled function is executed, either on the leading edge (at the start) or the trailing edge (at the end) of the interval.

Here's how you can implement that:

function throttle(func, limit, options = { leading: true, trailing: true }) {
  let lastFunc, lastRan;
 
  return function(...args) {
    const context = this;
    const now = Date.now();
 
    if (!lastRan) {
      if (options.leading) {
        func.apply(context, args);
      }
      lastRan = now;
    } else {
      clearTimeout(lastFunc);
      if (options.trailing) {
        lastFunc = setTimeout(function() {
          if ((now - lastRan) >= limit) {
            func.apply(context, args);
            lastRan = now;
          }
        }, limit - (now - lastRan));
      }
    }
  };
}
Key Points:
  • Leading and Trailing Edge Control: Use the options parameter to specify whether the function should be called on the leading edge, trailing edge, or both.

Usage Example with Options

Let's enhance the previous example to allow execution on both leading and trailing edges:

const throttledResize = throttle(handleResize, 200, { leading: true, trailing: true });
window.addEventListener('resize', throttledResize);

Coding Challenge: Throttle with Immediate Cancellation

For a deeper understanding, try this coding challenge:

Challenge: Modify the throttle function to include a method that allows immediate cancellation of the throttled function.

function throttle(func, limit, options = { leading: true, trailing: true }) {
  let lastFunc, lastRan;
  let context, args;
 
  const throttled = function(...params) {
    context = this;
    args = params;
    const now = Date.now();
 
    if (!lastRan) {
      if (options.leading) {
        func.apply(context, args);
      }
      lastRan = now;
    } else {
      clearTimeout(lastFunc);
      if (options.trailing) {
        lastFunc = setTimeout(function() {
          if ((now - lastRan) >= limit) {
            func.apply(context, args);
            lastRan = now;
          }
        }, limit - (now - lastRan));
      }
    }
  };
 
  throttled.cancel = function() {
    clearTimeout(lastFunc);
    lastRan = 0;
    lastFunc = null;
  };
 
  return throttled;
}
 
// Example usage with a function that can be canceled
const handleResize = () => console.log('Window resized');
const throttledResize = throttle(handleResize, 200, { leading: true, trailing: true });
 
window.addEventListener('resize', throttledResize);
 
// To cancel the throttled function
// throttledResize.cancel();

In this challenge:

  • Enhance the throttle function to include a cancel method, allowing you to cancel any pending executions.