back to series Implement a debounce function Mar 26, 2024
Debouncing is a technique used to control how many times we allow a function to be executed over time.
When a JavaScript function is debounced with a wait time of X milliseconds, it must wait until after X milliseconds have elapsed since the debounced function was last called.
From my personal experience, the implementation of debouncing with following variations are usally asked:
Variation: Basic implementation export default function debounce ( func , wait ) {
function debouncedFnImpl ( ... args ) {
timerId = setTimeout (() => {
func. call (thisArg, ... args);
// Usage of debounce function
const debouncedFn = debounce (() => {
console. log ( 'I am called' );
const input = document. getElementById ( 'input' );
input. addEventListener ( 'change' , debouncedFn);
Variation: With Immediate Flag Immediate flag denotes that the function for the first time must be invoked immediately without any delay. For further invocations, it follows same rules as mentioned above (basic implementation).
export default function debounce ( func , wait , immediate ) {
let hasBeenCalledImmediately = false ;
function debouncedFnImpl ( ... args ) {
// Check for the `immediate` flag and invoke immediately
hasBeenCalledImmediately = true ;
func. call (thisArg, ... args);
timerId = setTimeout (() => {
func. call (thisArg, ... args);
// Usage with immediate flag
const debouncedFn = debounce (
console. log (e.target.value);
const input = document. getElementById ( 'input' );
input. addEventListener ( 'change' , debouncedFn);
Variation: With Leading Flag Leading flag denotes that the function must be called on the leading edge , i.e, invoke immediately (like immediate flag), but not just for the first time.
In basic implementation we are doing a trailing edge debounce of the function.
export default function debounce ( func , wait , leading ) {
function debouncedFnImpl ( ... args ) {
const canCallNow = leading && ! timerId;
// Check for the `canCallNow` flag and invoke immediately
func. call (thisArg, ... args);
timerId = setTimeout (() => {
func. call (thisArg, ... args);
// Usage with leading flag
const debouncedFn = debounce (
console. log (e.target.value);
const input = document. getElementById ( 'input' );
input. addEventListener ( 'change' , debouncedFn);
Variation: With Flush and Cancel Methods This variation has few add-ons on the basic implementation and typically involves addition of two methods:
cancel()
: method to cancel pending invocations.
flush()
: method to immediately invoke any delayed invocations.
export default function debounce ( func , wait ) {
let invocationArgs = undefined ;
func. call (thisArg, ... invocationArgs);
function debouncedFnImpl ( ... args ) {
// we first cancel any pending invocations
// we then create delayed execution of the function (trailing edge invocation)
timerId = setTimeout (() => {
// attach cancel and flush utility methods
debouncedFnImpl.cancel = cancel;
debouncedFnImpl.flush = invoke;
// Usage with leading flag
const debouncedFn = debounce (
console. log (e.target.value);
const input = document. getElementById ( 'input' );
input. addEventListener ( 'change' , debouncedFn);
// Using the cancel method : it cancels any delayed invocations
// Using the flush method: any pending invocation if present is called immediately
I strongly encourage you to explore and tackle additional questions in my Closure Questions for Frontend Interviews blog series.
By doing so, you can enhance your understanding and proficiency with closures, preparing you to excel in your upcoming frontend interviews.
Wishing you best. Happy Interviewing 🫡.