Hone logo
Hone
Problems

Implement Throttle with Trailing Edge in JavaScript

You're tasked with creating a throttle function in JavaScript. Throttling is a technique used to limit the rate at which a function can be called. Specifically, this challenge requires implementing a throttle with a "trailing edge" behavior. This means that if the throttled function is called multiple times within the specified delay, the last call within that interval should be executed after the delay has passed. This is useful for scenarios like handling resize events or scroll events where you only want to react to the final state after a burst of activity.

Problem Description

Implement a JavaScript function called throttle that takes two arguments:

  1. func: The function to be throttled.
  2. delay: The time in milliseconds to wait before executing func.

The throttle function should return a new function that, when invoked, will execute func at most once every delay milliseconds. The key requirement is the "trailing edge" behavior:

  • If the throttled function is called multiple times in rapid succession (within the delay period), func should be executed once after the delay has elapsed since the last invocation of the throttled function.
  • If the throttled function is called and then not called again for a period longer than delay, func should be executed immediately upon its first invocation (or after the previous delay has passed if it was a trailing call).

Key Requirements:

  • The throttled function should preserve the this context of the original invocation.
  • The throttled function should pass any arguments from the invocation to the original func.
  • The delay should be a non-negative number.

Expected Behavior:

Imagine a scenario where a user is resizing a window rapidly. You want to update some UI element based on the window's dimensions, but you don't want to do it on every single pixel change. You only care about the final dimensions after the user stops resizing. This is where trailing-edge throttling shines.

Edge Cases to Consider:

  • What happens if throttle is called with a delay of 0 or less?
  • What if the throttled function is never called?
  • What if the throttled function is called only once?
  • How does it behave with multiple, subsequent calls within the delay period?

Examples

Example 1:

function logMessage(message) {
  console.log(message, new Date().toLocaleTimeString());
}

const throttledLog = throttle(logMessage, 1000);

// Simulate rapid calls
throttledLog("Call 1");
setTimeout(() => throttledLog("Call 2"), 200);
setTimeout(() => throttledLog("Call 3"), 500);
setTimeout(() => throttledLog("Call 4"), 800);
setTimeout(() => throttledLog("Call 5"), 1100); // This call will trigger the trailing execution

// Expected Output (approximately, timestamps will vary):
// After 1100ms from the start: Call 5 <timestamp>
// The previous calls (1-4) are effectively ignored in favor of Call 5.

Explanation: The first call to throttledLog doesn't immediately execute logMessage. Instead, it starts a timer. Subsequent calls within the 1000ms delay reset the timer for the trailing execution. The logMessage("Call 5") is the last call within a 1000ms window of the previous call, so logMessage is executed with the arguments of "Call 5" roughly 1000ms after "Call 5" was invoked.

Example 2:

function greet(name) {
  console.log(`Hello, ${name}!`);
}

const throttledGreet = throttle(greet, 500);

// Simulate calls with a longer pause
throttledGreet("Alice"); // Executes immediately or after previous delay if any
setTimeout(() => throttledGreet("Bob"), 600); // This call is more than 500ms after the previous, so it executes

// Expected Output (approximately):
// Hello, Alice! <timestamp>
// Hello, Bob! <timestamp>

Explanation: The first call to throttledGreet("Alice") is not followed by another call within 500ms. Therefore, "Hello, Alice!" is logged. The second call to throttledGreet("Bob") happens 600ms after the first, which is greater than the delay of 500ms. So, greet("Bob") is executed.

Example 3: Resetting the timer and this context

function processData(data) {
  console.log(`Processing: ${data} with this: ${this.name}`);
}

const obj = {
  name: "MyProcessor",
  process: throttle(processData, 700)
};

obj.process("Initial Data");
setTimeout(() => obj.process("Intermediate Data"), 300);
setTimeout(() => obj.process("Final Data"), 600); // This will trigger the trailing execution

// Expected Output (approximately):
// Processing: Final Data with this: MyProcessor <timestamp>

Explanation: The first call obj.process("Initial Data") sets up a timer. The subsequent calls within 700ms update the pending execution. The last call, obj.process("Final Data") at 600ms, becomes the one to be executed after the 700ms delay from that point. Crucially, the this context (obj.name) is preserved for the processData function.

Constraints

  • delay will be a non-negative integer representing milliseconds.
  • func will be a valid JavaScript function.
  • The throttled function should ideally not consume excessive CPU resources when idle.
  • Consider performance implications for very frequent calls and large delays.

Notes

  • Think about how you will manage timers (setTimeout, clearTimeout).
  • You'll need to keep track of whether a timer is currently active and when the last invocation occurred.
  • The trailing edge behavior means the function might not execute immediately on the first call if it's part of a rapid sequence.
  • Consider how to handle the case where the throttled function is called and then never called again within the delay. It should eventually execute.
Loading editor...
javascript