Home > Article > Web Front-end > Debouncing and Throttling
Another one among the popular frontend interview questions. It tests interviewees knowledge on JS, Performance and FE System Design.
This is question #2 of Frontend Interview Questions series. If you're looking to level up your preparation or stay updated in general, consider signing up on FrontendCamp.
Debouncing and Throttling work on the same principle - delay stuff - but still have very different approach and use cases.
Both the concepts are useful for developing a performant application. Almost all the websites you visit on a daily basis use Debouncing and Throttling in some way or the other.
A well known use case of debouncing is a typeahead(or autocomplete).
Imagine you are building a search feature for an E-commerce website that has thousands of products. When a user tries to search for something, your app would make an API call to fetch all the products that match the user's query string.
const handleKeyDown = async (e) => { const { value } = e.target; const result = await search(value); // set the result to a state and then render on UI } <Input onKeyDown={handleKeyDown} />
This approach looks fine but it has some issues:
The solution to these problems is Debouncing.
The basic idea is to wait until the user stops typing. We'll delay the API call.
const debounce = (fn, delay) => { let timerId; return function(...args) { const context = this; if (timerId) { clearTimeout(timerId); }; timerId = setTimeout(() => fn.call(context, ...args), delay); } } const handleKeyDown = async (e) => { const { value } = e.target; const result = await search(value); // set the result to a state and then render on UI } <Input onKeyDown={debounce(handleKeyDown, 500)} />
We've extended our existing code to make use of debouncing.
The debounce function is generic utility function that takes two arguments:
Inside the function, we use setTimeout to delay the actual function(fn) call. If the fn is called again before timer runs out, the timer resets.
With our updated implementation, even if the user types 15 characters we would only make 1 API call(assuming each key press takes less than 500 milliseconds). This solves all the issues we had when we started building this feature.
In a production codebase, you won't have to code your own debounce utility function. Chances are your company already uses a JS utility library like lodash that has these methods.
Well, Debouncing is great for performance but there a some scenarios where we don't want to wait for x seconds before being notified of a change.
Imaging you're building a collaborative workspace like Google Docs or Figma. One of the key features is a user should be aware of changes made my other users in real time.
So far we only know of two approaches:
This is where Throttling comes in. It's right in middle of the two approaches mentioned above. The basic idea is - notify on periodic intervals - not in the end and not on each key press, but periodically.
const throttle = (fn, time) => { let lastCalledAt = 0; return function(...args) { const context = this; const now = Date.now(); const remainingTime = time - (now - lastCalledAt); if (remainingTime <= 0) { fn.call(context, ...args); lastCalledAt = now; } } } const handleKeyDown = async (e) => { const { value } = e.target; // save it DB and also notify other peers await save(value); } <Editor onKeyDown={throttle(handleKeyDown, 1000)} />
We've modified our existing code to utilise throttle function. It takes two arguments:
The implementation is straight-forward. We store the time when the function was last called in lastCalledAt. Next time, when a function call is made, we check if time has passed and only then we execute fn.
We're almost there, but this implementation has a bug. What if last function call with some data is made within the time interval and no call is made after that. With our current implementation, we will lose some data.
To fix this, we'll store the arguments in another variable and initiate a timeout to be called later if no event is received.
const throttle = (fn, time) => { let lastCalledAt = 0; let lastArgs = null; let timeoutId = null; return function(...args) { const context = this; const now = Date.now(); const remainingTime = time - (now - lastCalledAt); if (remainingTime <= 0) { // call immediately fn.call(context, ...args); lastCalledAt = now; if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } } else { // call later if no event is received lastArgs = args; if (!timeoutId) { timeoutId = setTimeout(() => { fn.call(context, ...lastArgs); lastCalledAt = Date.now(); lastArgs = null; timeoutId = null; }, remainingTime); } } } }
This updated implementation makes sure we don't miss out on any data.
Lodash also provides a throttle utility function.
FrontendCamp
lodash
The above is the detailed content of Debouncing and Throttling. For more information, please follow other related articles on the PHP Chinese website!