Good evening! Here's our prompt for today.
Both debouncing and throttling are intended to enhance a website's performance. They accomplish this primarily by limiting the number of times a function can execute.
In the case of throttling, the exact definition is that it's a technique where a function can be executed only once in any given time interval-- regardless of how many times the user fires the event.
When is this useful? Consider the example of infinite scroll. Each time the user scrolls to the bottom of the page, we'll want to execute a function to grab the next list of resources to display. However, it's really easy for a user to scroll back up a bit and then try again, possibly several times, if the results don't load fast enough. If we throttle that call, we won't make unnecessary, expensive network calls.
Please use the following to test your code, comments in-line:
1var assert = require('assert');
2
3function throttle(func, waitTime) {
4 // fiill in this method
5}
6
7let time = 0;
8
9function testThrottle(input) {
10 const calls = [];
11 time = 0;
12
13 function wrapper(arg) {
14 calls.push(`${arg}@${time}`);
15 }
16
17 const throttledFunc = throttle(wrapper, 3);
18 input.forEach((call) => {
19 const [arg, time] = call.split("@");
20 setTimeout(() => throttledFunc(arg), time);
21 });
22 return calls;
23}
24
25expect(testThrottle(["A@0", "B@2", "C@3"])).toEqual(["A@0", "C@3"]);

Try to solve this here or in Interactive Mode.
How do I practice this challenge?
xxxxxxxxxx
function throttle(func, waitTime) {
// Set isThrottling flag to false to start
// and savedArgs to null
let isThrottling = false,
savedArgs = null;
// Spread the arguments for .apply
return function (args) {
// Return a wrapped function
// Flag preventing immediate execution
if (!isThrottling) {
// Actual initial function execution
func.apply(this, args);
// Flip flag to throttling state
isThrottling = true;
// Queue up timer to flip the flag so future iterations can occur
function queueTimer() {
setTimeout(() => {
// Stop throttling
isThrottling = false;
// Queueing up the next invocation after wait time passes
if (savedArgs) {
func.apply(this, savedArgs);
isThrottling = true;
savedArgs = null;
queueTimer();
}
}, waitTime);
}
queueTimer();
Here's our guided, illustrated walk-through.
How do I use this guide?
Solution
During implementation, the first thing to consider is how we prevent multiple function calls from executing. A very simple way is by using a boolean
flag, like isThrottling
, to check if we should execute the block. It can be initialized as false
, and during the first execution of a function, we can set it to true
. Then, the next time the function is invoked, nothing will happen.
1function throttle(func, waitTime) {
2 let isThrottling = false;
3
4 return () => {
5 if (!isThrottling) {
6 // run the code
7 func.apply(this);
8 isThrottling = true;
9 }
10 };
11}
But of course, this will set the isThrottling
flag to true
, and all future calls will run into this issue and not execute:
1const testCall = throttle(() => console.log("it will run once"));
2testCall(); // it will run once
3testCall(); // undefined
We'll need to programmatically reset the flag after a certain time. The most obvious way to do this is to use the setTimeout
method to flip the boolean after a certain amount of time. Let's add this on.
1function throttle(func, waitTime) {
2 let isThrottling = false;
3
4 return () => {
5 if (!isThrottling) {
6 // Run the code
7 func.apply(this);
8 isThrottling = true;
9
10 // Queue up timer to flip the flag so future iterations can occur
11 function queueTimer() {
12 setTimeout(() => {
13 isThrottling = false;
14 }, waitTime);
15 }
16 queueTimer();
17 }
18 };
19}
We're now at a point where we've wrapped the function in a closure that manipulates the isThrottling
flag. The flag will prevent any other calls from executing until a certain time period has passed. Now let's start wrapping this up by making sure the function is indeed invoked again after the waitTime
finishes.
One other thing we haven't considered-- what happens in the event that the user invokes the functon during the throttling period, using different arguments? We want to save that call and wait the allotted time before executing. To do this, we'll need to save the new arguments in a savedArgs
method.
Please follow the comments to see what's happening:
1function throttle(func, waitTime) {
2 // Set isThrottling flag to false to start
3 // and savedArgs to null
4 let isThrottling = false,
5 savedArgs = null;
6 // Spread the arguments for .apply
7 return function (...args) {
8 // Return a wrapped function
9 // Flag preventing immediate execution
10 if (!isThrottling) {
11 // Actual initial function execution
12 func.apply(this, args);
13 // Flip flag to throttling state
14 isThrottling = true;
15 // Queue up timer to flip the flag so future iterations can occur
16 function queueTimer() {
17 setTimeout(() => {
18 // Stop throttling
19 isThrottling = false;
20 // Queueing up the next invocation after wait time passes
21 if (savedArgs) {
22 func.apply(this, savedArgs);
23 isThrottling = true;
24 savedArgs = null;
25 queueTimer();
26 }
27 }, waitTime);
28 }
29 queueTimer();
30 }
31 // Wait state until timeout is done
32 // Save arguments
33 else savedArgs = args;
34 };
35}
One Pager Cheat Sheet
Throttling
is a technique to limit the number of times a function can execute in any given interval, which is useful for cases likeinfinite scrolling
where a function needs to be executed only once per user action.- By using a
boolean
flag likeisThrottling
and thesetTimeout
method, we can create athrottle
function that executes a given function after a certain amount of time, and save any further calls with different arguments for later invocation.
This is our final solution.
To visualize the solution and step through the below code, click Visualize the Solution on the right-side menu or the VISUALIZE button in Interactive Mode.
xxxxxxxxxx
}
function throttle(func, waitTime) {
// Set isThrottling flag to false to start
// and savedArgs to null
let isThrottling = false,
savedArgs = null;
// Spread the arguments for .apply
return function (args) {
// Return a wrapped function
// Flag preventing immediate execution
if (!isThrottling) {
// Actual initial function execution
func.apply(this, args);
// Flip flag to throttling state
isThrottling = true;
// Queue up timer to flip the flag so future iterations can occur
function queueTimer() {
setTimeout(() => {
// Stop throttling
isThrottling = false;
// Queueing up the next invocation after wait time passes
if (savedArgs) {
func.apply(this, savedArgs);
isThrottling = true;
savedArgs = null;
queueTimer();
}
}, waitTime);
}
queueTimer();
}
// Wait state until timeout is done
// Save arguments
Got more time? Let's keep going.
If you had any problems with this tutorial, check out the main forum thread here.