Throttle

Updated on 15 July, 2025
Throttle header image

Problem Statement

In this problem, we receive a function fn and a time interval t in milliseconds. Our task is to modify this function so that it operates in a throttled manner. Throttling behavior, in this context, means that the modified function (throttleFn) will call fn at its first invocation without delay but subsequently prevent additional executions during a defined time period t. If additional calls are made to throttleFn during this period, the latest arguments from these calls will be held and used once the period ends. Thus, the process involves managing the execution in slots of time to avoid excessive, rapid function calls, ultimately regulating the usage pattern.

For example, with a throttle time t = 50ms, and function calls at 30ms, 40ms, and 60ms, initially, the function executes at 30ms without any delay. However, an invocation at 40ms during the active throttle will not execute immediately but will instead store its arguments. If another invocation is made at 60ms, it overwrites the previous stored arguments since it occurs within the same throttling period. Once the initial throttle period concludes at 80ms (from 30ms), the function then executes with the latest stored parameters from the 60ms call. This rhythm continues with each execution phase reinitiating the throttle period.

Examples

Example 1

Input:

t = 100,
calls = [
{"t":20,"inputs":[1]}
]

Output:

[{"t":20,"inputs":[1]}]

Explanation:

The 1st call is always called without delay

Example 2

Input:

t = 50,
calls = [
{"t":50,"inputs":[1]},
{"t":75,"inputs":[2]}
]

Output:

[{"t":50,"inputs":[1]},{"t":100,"inputs":[2]}]

Explanation:

The 1st is called a function with arguments (1) without delay.
The 2nd is called at 75ms, within the delay period because 50ms + 50ms = 100ms, so the next call can be reached at 100ms. Therefore, we save arguments from the 2nd call to use them at the callback of the 1st call.

Example 3

Input:

t = 70,
calls = [
{"t":50,"inputs":[1]},
{"t":75,"inputs":[2]},
{"t":90,"inputs":[8]},
{"t": 140, "inputs":[5,7]},
{"t": 300, "inputs": [9,4]}
]

Output:

[{"t":50,"inputs":[1]},{"t":120,"inputs":[8]},{"t":190,"inputs":[5,7]},{"t":300,"inputs":[9,4]}]

Explanation:

The 1st is called a function with arguments (1) without delay.
The 2nd is called at 75ms within the delay period because 50ms + 70ms = 120ms, so it should only save arguments. 
The 3rd is also called within the delay period, and because we need just the latest function arguments, we overwrite previous ones. After the delay period, we do a callback at 120ms with saved arguments. That callback makes another delay period of 120ms + 70ms = 190ms so that the next function can be called at 190ms.
The 4th is called at 140ms in the delay period, so it should be called as a callback at 190ms. That will create another delay period of 190ms + 70ms = 260ms.
The 5th is called at 300ms, but it is after 260ms, so it should be called immediately and should create another delay period of 300ms + 70ms = 370ms.

Constraints

  • 0 <= t <= 1000
  • 1 <= calls.length <= 10
  • 0 <= calls[i].t <= 1000
  • 0 <= calls[i].inputs[j], calls[i].inputs.length <= 10

Approach and Intuition

Given the throttling constraints and the need to store and manage function calls over time, we can grasp the functioning of such system through our examples:

  1. First Call Execution:

    • The first function call is executed immediately, irrespective of the throttle period. This is derived based on functionality descriptions provided, ensuring that there is an initial immediate response.
  2. Subsequent Calls within Throttle Limit:

    • Any function call that comes in during an active throttle period doesn't execute immediately but has its input parameters stored.
    • If multiple calls come in during the same throttle period, only the latest call's parameters are kept, overwriting any previously stored parameters from other calls within the same period.
  3. Execution Post Throttle:

    • At the end of the throttle period, fn is executed with the latest stored parameters. Simultaneously, this execution restarts the throttle timer for another t milliseconds, where further incoming calls would again wait for the next cycle.

The examples reflect varied throttle timings and calls timings. The first example shows a straightforward scenario with a single call and a long interval. The second and third examples involve overlapping calls where only some calls lead to execution and the others contribute through their arguments, showcasing efficient function call management by minimizing redundancy and preserving system resources during high-frequency events.

The approach involves:

  • Immediate execution on the first call.
  • Blocking direct execution of subsequent calls within a throttle period while capturing and updating the latest intended parameters.
  • Post-throttle period execution using the most recent parameters and reinitialization of the throttle timer.

This method is effective in managing resources, reducing unnecessary executions, and ensuring the most up-to-date call parameters are used when eventually executing fn.

Solutions

  • JavaScript
js
var throttleController = function(func, delayTime) {
  let timeoutRef = null;
  let nextAllowedCall = 0;
  return function(...params) {
    const waitTime = Math.max(0, nextAllowedCall - Date.now());
    clearTimeout(timeoutRef);
    timeoutRef = setTimeout(() => { 
      func(...params);
      nextAllowedCall = Date.now() + delayTime;
    }, waitTime);
  }
};

The "Throttle" solution provides a JavaScript function named throttleController which controls how often a specified function (func) can be called. It uses two parameters: func, the function to limit the calls to, and delayTime, the minimum time interval between successive calls to func.

Here's how the throttling mechanism works:

  • Initialize timeoutRef to null, which will hold the reference to setTimeout.
  • nextAllowedCall keeps track of when func can be called next.
  • If the function wrapped by throttleController is called multiple times, calculate waitTime, the time to wait before the next call.
  • Clear any previous timeout to prevent multiple instances of func from executing due to previous calls.
  • Set up a new timeout (stored in timeoutRef) to execute func after waitTime has elapsed.
  • Update nextAllowedCall to mark the next permissible time for execution.

After integrating this utility, avoid frequent or unnecessary calling of sensitive functions which might lead to performance bottlenecks. This is particularly useful in scenarios such as handling scroll or resize events in web development where the event can fire at a high rate.

Comments

No comments yet.