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:
lastCallTime: The timestamp (Date.now()orperformance.now()) when the throttled function was most recently invoked by your code.lastInvokeTime: The timestamp when the original target callback (func) was last actually executed.wait: The minimum delay specified by the caller.maxWait: Equal towaitin throttled functions, enforcing an upper bound on how long execution can be delayed.timerId: A reference to the activesetTimeoutinstance managing the pending execution.
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:
- The time remaining based on the user's last invocation
(
wait - (time - lastCallTime)). - 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:
trailing: trueMust Be Enabled: This option is enabled by default in_.throttle. If explicitly set tofalse, the trailing call is suppressed entirely.- 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). - Elapsed Time Meets or Exceeds
wait: If the calculatedremainingWaitis less than or equal to0, Lodash callsinvokeFunc(time). This invokes the original function, resetslastArgsandlastThis, and updateslastInvokeTimeto 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.