cf.completefrontend

Write the codeMedium

Write throttle(fn, wait) so that fn runs at most once every wait ms.

Before you start

What's throttling?

Some events fire continuously while something is going on: scroll while the page moves, pointermove while the pointer moves, resize while a window is resized. Doing slow work on every one of them can make the page stutter. Throttling a function limits how often it can run: at most once per interval, say once every 100 ms, however often it's called. While the calls keep coming, it runs at a steady pace, so the page keeps updating, only less often.

Picture a turnstile that lets one person through, then stays locked for a few seconds. However hard the crowd pushes, people get through at a steady rate.

Here's a scroll handler throttled to once every 100 ms. Scrolling calls it five times, with a to e standing for the scroll position each time, then stops. Throttles come in two flavours, and both run the first call right away. The first then drops every call made less than 100 ms after it last ran. The second opens a 100 ms window, keeps the latest call made during it, and runs that call, the trailing call, when the window ends:

TimeCallThrottle that drops callsThrottle with a trailing call
0 msaRuns with a.Runs with a, and a window opens.
40 msbDropped: it ran 40 ms ago.Doesn't run. b is kept as the latest call.
80 mscDropped: it ran 80 ms ago.Doesn't run. c replaces b.
100 msNoneNothing happens.The window ends: runs with c, and a new window opens.
130 msdRuns with d: it last ran 130 ms ago.Doesn't run. d is kept.
170 mseDropped: it ran 40 ms ago.Doesn't run. e replaces d.
200 msNoneNothing happens.The window ends: runs with e, and a new window opens.
300 msNoneNothing happens.The window ends with no call in it, so the throttle goes idle. The next call runs right away.

Both run at most once every 100 ms. The first ran with a and d, and dropped e, the last call, so if e was where the scrolling stopped, the page never shows it. The second ran with a, c, and e: a little later, but the final state always gets through.

Throttling is often confused with debouncing, which waits for the calls to stop and then runs once. Given the same five calls, a debounce with a 100 ms wait would run only once, at 270 ms, with e: during a long stream of calls, it doesn't run at all. Throttle when you want updates while something is happening: a reading-progress bar that fills as the page scrolls, or loading the next page of a list as the user nears the bottom.

The first call runs fn right away and opens a window of wait ms.

Calls made during the window don't run fn. If there were any, fn runs once when the window ends, with the this and arguments of the latest of them, and that run opens a new window. If there were none, the throttle goes idle, and the next call runs fn right away again.

Add a cancel() method that drops a pending call and resets the throttle, so the next call runs fn right away.

Write this
function throttle(fn, wait) {
  // your code
}
 
// const log = throttle((n) => console.log(n), 100);
// log(1); // logs 1 right away and opens a 100 ms window
// log(2); // during the window: logs nothing
// log(3); // during the window: logs nothing
// // 100 ms later: logs 3, the latest call, and a new window opens
//
// const onScroll = throttle(() => console.log(window.scrollY), 100);
// window.addEventListener("scroll", onScroll);
// // logs at most every 100 ms while scrolling, and once more at the end

Solve it in the editor, with tests

The answer

Write it yourself first. Then compare it with a reference solution and the checks it passes.

Checking your sign-in…