Lodash Throttle Trailing Edge Execution Timing

This article explains how the Lodash JavaScript library determines the exact execution timing of the final trailing callback in _.throttle. It breaks down the underlying reliance on _.debounce, the tracking of internal timestamps, the mathematical calculation behind remainingWait, and how the browser's event loop ultimately resolves the scheduled invocation.

Throttle Is Built on Debounce

In Lodash, _.throttle is not a standalone implementation; it is a wrapper around _.debounce. Calling _.throttle(func, wait, options) invokes debounce with specific defaults:

_.debounce(func, wait, {
  leading: options.leading !== false,
  maxWait: wait,
  trailing: options.trailing !== false
});

Because maxWait is set to the same value as wait, the execution mechanics of _.throttle—including its trailing edge—are strictly governed by the debouncing engine's timer and delay calculations.

The State Variables That Control Timing

Lodash maintains several internal variables to evaluate whether and when to fire the trailing edge:

The remainingWait Calculation

When the throttled function is called repeatedly, Lodash recalculates how long it must wait before it can fire again. It uses an internal function named remainingWait(time):

function remainingWait(time) {
  const timeSinceLastCall = time - lastCallTime;
  const timeSinceLastInvoke = time - lastInvokeTime;
  const timeWaiting = wait - timeSinceLastCall;

  return maxing
    ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
    : timeWaiting;
}

Because maxing is always true for _.throttle, the remaining delay until the trailing callback is the smaller of two values:

  1. The time remaining based on the user's last invocation (wait - (time - lastCallTime)).
  2. The time remaining before hitting the maximum threshold (maxWait - (time - lastInvokeTime)).

Conditions for the Trailing Edge Execution

The trailing callback is scheduled through a standard setTimeout using the duration returned by remainingWait. When the timer expires, the internal timerExpired function executes and checks whether the trailing callback should run:

  1. trailing: true Must Be Enabled: This option is enabled by default in _.throttle. If explicitly set to false, the trailing call is suppressed entirely.
  2. An Unhandled Call Must Exist: Lodash checks if lastCallTime !== undefined. A trailing callback only executes if the throttled function was called at least once after the most recent invocation (the leading edge or a prior interval execution).
  3. Elapsed Time Meets or Exceeds wait: If the calculated remainingWait is less than or equal to 0, Lodash calls invokeFunc(time). This invokes the original function, resets lastArgs and lastThis, and updates lastInvokeTime to the current timestamp.

If additional invocations occurred while the timer was ticking, but the cooldown period has not fully elapsed, Lodash restarts the timer with the updated remainingWait duration instead of executing immediately.

Event Loop Precision

The exact millisecond timing of the final trailing call is ultimately subject to JavaScript's concurrency model. Because Lodash relies on setTimeout to schedule the trailing edge check, execution can be deferred beyond the computed wait window if the main thread is blocked by heavy synchronous tasks or long-running microtasks. The trailing callback executes on the earliest macrotask turn available once the timer elapses.