Table of Contents

Debounce
A debounce function takes a function & returns an optimized version of it. The optimized version groups a sudden burst of function calls into 1.
How It Works
When debouncedFunc
is invoked, a timer will start.
When the timer completes, func
is invoked.
If, however, debouncedFunc
is invoked again before the timer completes, the timer will restart.
/index.js
Console
function debounce(callback, waitMS = 200) { let timeoutId; return function(...args) { const context = this clearTimeout(timeoutId); timeoutId = setTimeout(function(){ timeoutId = null callback.call(context, ...args) }, waitMS); }; }; function func(x) { console.log(x); } const debouncedFunc = debounce(func) // Will be called debouncedFunc(1); // Won't be called because of debouncing debouncedFunc(1); // Will be called because it is called after the debounce limit has expired from the initial call above setTimeout(() => debouncedFunc(1), 200);
There are 2 types of debounce implementations:
- ▪ Trailing Edge: The above implementation.
func
is invoked AFTER the timer has completed. - ▪ Leading Edge:
func
is invoked BEFORE the timer starts. When the timer completes,func
will not be invoked. Implementation below.
/index.js
Console
function debounce(callback, isLeadingEdge = false, waitMS = 200) { let timeoutId; return function(...args) { const context = this const isCallNow = isLeadingEdge && !timeoutId clearTimeout(timeoutId); timeoutId = setTimeout(function(){ timeoutId = null if (!isLeadingEdge) { callback.call(context, ...args) } }, waitMS); if (isCallNow) { callback.call(context, ...args); } }; }; function func(x) { console.log(x); } const debouncedFunc = debounce(func, true) // Will be called debouncedFunc(1); // Won't be called because of debouncing debouncedFunc(1); // Will be called because it is called after the debounce limit has expired from the initial call above setTimeout(() => debouncedFunc(1), 200);
Use Case
Input validation. Imagine a form with an email input. Each time the user enters a character, a validation function is invoked. If the input value is not in a valid email format, it renders a error message.
This UX isn't great. An error message will be shown as soon as the 1st character is entered into the input. An improvement would be to instead invoke the validation function only after the user has finished entering all characters of their email address. This can be done by debouncing the validation function.
Throttle
A throttle function takes a function & returns an optimized version of it. The optimized version prevents a function being called more than once every X milliseconds.
How It Works
When throttledFunc
is invoked, func
will be invoked & a timer will be started.
Until the timer completes, any calls to throttledFunc
will not invoke func
.
/index.js
Console
function throttle (func, waitMS = 200) { let isWait = false; return function(...args) { if (!isWait) { func.call(this, ...args); isWait = true; setTimeout(() => { isWait = false; }, waitMS); } } } function func(x) { console.log(x); } const throttledFunc = throttle(func) // Will be called throttledFunc(1); // Won't be called because of throttling throttledFunc(1); // Will be called because it is called after the throttle limit has expired from the initial call above setTimeout(() => throttledFunc(1), 200);
Use Case
Window scroll callback. Imagine you set a callback for the window scroll event. It could be invoked up to 30 times per second. Depending on what the callback was doing, a lot of these could redundant. While the browser is processing all them, it could block execution, making the browser unresponsive. Throttling the callback would reduce the number of invokes.
requestAnimationFrame
window.requestAnimationFrame
is a method provided by the browser that will throttle a function.
It requests the browser invokes a function before the next repaint.
requestAnimationFrame
can be thought of as a throttle with waitMS = 16
(60fps).
However, internally will decide the best timing on how to schedule the rendering.
It has a much higher fidelity than a common throttle being a browser native API that aims for better accuracy.
When a property like opacity is changed, the user won't see that change until the browser does a repaint.
Imagine you have an element with an opacity of 1.
You create a function that change that element's opacity.
You invoke the function twice, once to change it to 0.9
, then again the change it to 0.8
.
If both of those calls occur before the browser does a repaint, then the 1st will have no effect.
The user will only see the element change from 1
to 0.8
.
These unrequired invokes can be avoided using requestAnimationFrame
.
How It Works
You pass a function to requestAnimationFrame
.
It won't be invoke until the browser is ready to make the next repaint.
Use Case
When a function is painting or animating properties & you want to guarantee smooth changes.
For example, changing the opacity of an element based on the scroll position.
Below, we use requestAnimationFrame
when a new value for opacity needs to be set.
If that value changes before a repaint, we cancel the request & set a new 1 with the latest value.
localhost:3000
const p = document.querySelector('p') const div = document.querySelector('div') function updateDOM(lastScrollY) { const divHeight = div.getBoundingClientRect().height const viewportHeight = window.innerHeight; const scrollFraction = lastScrollY / (divHeight - viewportHeight); p.style.opacity = 1 - scrollFraction } function rAFThrottle(callback) { let requestID return function(...args) { const context = this cancelAnimationFrame(requestID) requestID = requestAnimationFrame(() => { callback.call(context, ...args) isWait = false }) } } const throttledUpdateDOM = rAFThrottle(updateDOM) function onScroll() { throttledUpdateDOM(window.scrollY) } window.addEventListener('scroll', onScroll)
Above, viewportHeight
is being calculated unnecessarily every update.
It couldn't be moved outside of the function.
Possibly related to iframe loading delay.
.call(..)
In the above examples, callback.call(..)
is used instead of callback(..)
.
This is to ensure the correct this
value is bound.
See JavaScript - this for more details.
Where to Next?



│└── JavaScript│└── PerformanceYOU ARE HERE│├── Debounce Throttle