Goal: create a
debounce(fn, timeout)function that takes a functionfnand a delaytimeout, and returns a new function that postpones the call tofnuntiltimeoutmilliseconds of inactivity have passed.
To solve this problem, you will need two key JavaScript functions:
setTimeout(): to schedule the execution of a function in the future.clearTimeout(): to cancel a previously scheduled execution if the function is called again.You will need to store the timer ID in a variable accessible through a closure.
/**
* @param {Function} fn - The original function
* @param {number} timeout - The delay in ms
*/
function debounce(fn, timeout) {
// A variable to store the timer ID.
// It is in a closure, so it retains its value between calls.
let timerId = null;
// Return a new wrapper function
return function(...args) {
// Save the `this` context and the arguments with which the wrapper was called
const context = this;
// If the timer was already started, cancel it.
// This is the key logic of debounce.
clearTimeout(timerId);
// Start a new timer.
timerId = setTimeout(() => {
// When the timer fires, call the original function,
// passing it the saved context and arguments.
fn.apply(context, args);
}, timeout);
};
}Why this way:
timerId variable “lives” between calls to the returned function, allowing you to manage the same timer.clearTimeout: This is the heart of the mechanism. Each new call resets the previous “plan” for execution, ensuring that only the last call in a series initiates execution.apply(context, args): This allows you to preserve the original context (this) and pass all arguments to the target function fn. Without this, this would be lost, and the arguments would not be passed.const debounce = (fn, timeout) => {
let timerId = null;
// Return an arrow function, which does not have its own `this`
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => {
// `this` here will be inherited from the outer scope, not from the call site.
// For simple functions without a context, this works perfectly.
fn(...args);
}, timeout);
};
};Why this way:
this context (e.g., when working with helper functions that are not object methods).fn is an object method (obj.method()) and uses this, this option will not work, as this will be lost. In such cases, you should use Solution #1.Write a debounce function that takes two arguments:
fn — the function whose execution needs to be delayed.timeout — the time in milliseconds that must pass without calls before fn is executed.The debounce function should return a new wrapper function. Each time this wrapper is called, the internal timer should be reset. The original function fn should be called only once — after timeout ms have passed since the last call.
// Example 1: Input Handling
const logger = (text) => console.log(`Sending request: ${text}`);
const debouncedLogger = debounce(logger, 500);
// User types "hello"
debouncedLogger('h');
debouncedLogger('he');
debouncedLogger('hel');
// ...500 ms pause...
// The console will show: "Sending request: hel" (only the last call)
// Example 2: Button Click
let clickCount = 0;
const handleClick = () => {
clickCount++;
console.log(`Button clicked ${clickCount} times`);
};
const debouncedClick = debounce(handleClick, 1000);
// User clicks quickly 5 times
// debouncedClick();
// debouncedClick();
// debouncedClick();
// ...
// After 1 second, the console will show: "Button clicked 1 time"debounce.fn only once.this and arguments to the original function fn.Goal: create a
debounce(fn, timeout)function that takes a functionfnand a delaytimeout, and returns a new function that postpones the call tofnuntiltimeoutmilliseconds of inactivity have passed.
To solve this problem, you will need two key JavaScript functions:
setTimeout(): to schedule the execution of a function in the future.clearTimeout(): to cancel a previously scheduled execution if the function is called again.You will need to store the timer ID in a variable accessible through a closure.
/**
* @param {Function} fn - The original function
* @param {number} timeout - The delay in ms
*/
function debounce(fn, timeout) {
// A variable to store the timer ID.
// It is in a closure, so it retains its value between calls.
let timerId = null;
// Return a new wrapper function
return function(...args) {
// Save the `this` context and the arguments with which the wrapper was called
const context = this;
// If the timer was already started, cancel it.
// This is the key logic of debounce.
clearTimeout(timerId);
// Start a new timer.
timerId = setTimeout(() => {
// When the timer fires, call the original function,
// passing it the saved context and arguments.
fn.apply(context, args);
}, timeout);
};
}Why this way:
timerId variable “lives” between calls to the returned function, allowing you to manage the same timer.clearTimeout: This is the heart of the mechanism. Each new call resets the previous “plan” for execution, ensuring that only the last call in a series initiates execution.apply(context, args): This allows you to preserve the original context (this) and pass all arguments to the target function fn. Without this, this would be lost, and the arguments would not be passed.const debounce = (fn, timeout) => {
let timerId = null;
// Return an arrow function, which does not have its own `this`
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => {
// `this` here will be inherited from the outer scope, not from the call site.
// For simple functions without a context, this works perfectly.
fn(...args);
}, timeout);
};
};Why this way:
this context (e.g., when working with helper functions that are not object methods).fn is an object method (obj.method()) and uses this, this option will not work, as this will be lost. In such cases, you should use Solution #1.Write a debounce function that takes two arguments:
fn — the function whose execution needs to be delayed.timeout — the time in milliseconds that must pass without calls before fn is executed.The debounce function should return a new wrapper function. Each time this wrapper is called, the internal timer should be reset. The original function fn should be called only once — after timeout ms have passed since the last call.
// Example 1: Input Handling
const logger = (text) => console.log(`Sending request: ${text}`);
const debouncedLogger = debounce(logger, 500);
// User types "hello"
debouncedLogger('h');
debouncedLogger('he');
debouncedLogger('hel');
// ...500 ms pause...
// The console will show: "Sending request: hel" (only the last call)
// Example 2: Button Click
let clickCount = 0;
const handleClick = () => {
clickCount++;
console.log(`Button clicked ${clickCount} times`);
};
const debouncedClick = debounce(handleClick, 1000);
// User clicks quickly 5 times
// debouncedClick();
// debouncedClick();
// debouncedClick();
// ...
// After 1 second, the console will show: "Button clicked 1 time"debounce.fn only once.this and arguments to the original function fn.The code editor is intentionally hidden on mobile.
Believe me, it's for the best: I am protecting you from the temptation to code in less-than-ideal conditions. A small screen and a virtual keyboard are not the best tools for a programmer.
📖 Now: Study the task, think through the solution. Act like a strategist.
💻 Later: Sit down at your computer, open the site, and implement all your ideas comfortably. Act like a code-jedi!