Debounce and Throttle
Debounce and throttle are techniques to control how often functions execute, improving performance and user experience.
Debounce
Debounce delays function execution until after a specified time has passed since the last call.
javascript// Basic debounce implementation function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; } // Usage example const searchInput = document.getElementById('search'); const debouncedSearch = debounce((query) => { console.log('Searching for:', query); // Perform API call here }, 300); searchInput.addEventListener('input', (e) => { debouncedSearch(e.target.value); }); // Advanced debounce with immediate execution option function advancedDebounce(func, delay, immediate = false) { let timeoutId; return function(...args) { const callNow = immediate && !timeoutId; clearTimeout(timeoutId); timeoutId = setTimeout(() => { timeoutId = null; if (!immediate) func.apply(this, args); }, delay); if (callNow) func.apply(this, args); }; }
Throttle
Throttle limits function execution to once per specified time interval.
javascript// Basic throttle implementation function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Usage example const throttledScroll = throttle(() => { console.log('Scroll position:', window.scrollY); }, 100); window.addEventListener('scroll', throttledScroll); // Advanced throttle with trailing execution function advancedThrottle(func, limit, options = {}) { let timeout; let previous = 0; return function(...args) { const now = Date.now(); if (!previous && options.leading === false) { previous = now; } const remaining = limit - (now - previous); if (remaining <= 0 || remaining > limit) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(this, args); } else if (!timeout && options.trailing !== false) { timeout = setTimeout(() => { previous = options.leading === false ? 0 : Date.now(); timeout = null; func.apply(this, args); }, remaining); } }; }
Practical Examples
javascript// Search with debounce function setupSearch() { const searchInput = document.getElementById('search-input'); const resultsContainer = document.getElementById('search-results'); const performSearch = debounce(async (query) => { if (query.length < 2) { resultsContainer.innerHTML = ''; return; } try { resultsContainer.innerHTML = 'Searching...'; // Simulate API call await new Promise(resolve => setTimeout(resolve, 500)); resultsContainer.innerHTML = `Results for: ${query}`; } catch (error) { resultsContainer.innerHTML = 'Search failed'; } }, 300); searchInput.addEventListener('input', (e) => { performSearch(e.target.value); }); } // Window resize handler with throttle function setupResizeHandler() { const throttledResize = throttle(() => { console.log('Window resized:', window.innerWidth, window.innerHeight); // Recalculate layouts, update charts, etc. }, 250); window.addEventListener('resize', throttledResize); } // Button click protection with debounce function setupFormSubmission() { const submitButton = document.getElementById('submit-btn'); const debouncedSubmit = debounce(async () => { submitButton.disabled = true; submitButton.textContent = 'Submitting...'; try { // Simulate form submission await new Promise(resolve => setTimeout(resolve, 2000)); console.log('Form submitted successfully'); } catch (error) { console.error('Submission failed:', error); } finally { submitButton.disabled = false; submitButton.textContent = 'Submit'; } }, 500); submitButton.addEventListener('click', debouncedSubmit); } // Scroll-based loading with throttle function setupInfiniteScroll() { let loading = false; const throttledScroll = throttle(() => { if (loading) return; const { scrollTop, scrollHeight, clientHeight } = document.documentElement; if (scrollTop + clientHeight >= scrollHeight - 100) { loading = true; loadMoreContent().then(() => { loading = false; }); } }, 200); window.addEventListener('scroll', throttledScroll); } async function loadMoreContent() { console.log('Loading more content...'); // Simulate API call await new Promise(resolve => setTimeout(resolve, 1000)); console.log('Content loaded'); } // Auto-save with debounce function setupAutoSave() { const editor = document.getElementById('editor'); let hasUnsavedChanges = false; const debouncedSave = debounce(async () => { if (!hasUnsavedChanges) return; console.log('Auto-saving...'); try { // Simulate save operation await new Promise(resolve => setTimeout(resolve, 500)); hasUnsavedChanges = false; console.log('Auto-save completed'); } catch (error) { console.error('Auto-save failed:', error); } }, 2000); editor.addEventListener('input', () => { hasUnsavedChanges = true; debouncedSave(); }); }
Utility Functions
javascript// Debounce with cancel method function cancellableDebounce(func, delay) { let timeoutId; const debounced = function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; debounced.cancel = function() { clearTimeout(timeoutId); }; return debounced; } // Throttle with cancel method function cancellableThrottle(func, limit) { let inThrottle; let timeoutId; const throttled = function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; timeoutId = setTimeout(() => inThrottle = false, limit); } }; throttled.cancel = function() { clearTimeout(timeoutId); inThrottle = false; }; return throttled; } // Usage with cancellation const debouncedFn = cancellableDebounce(() => console.log('Debounced'), 1000); const throttledFn = cancellableThrottle(() => console.log('Throttled'), 1000); // Cancel if needed // debouncedFn.cancel(); // throttledFn.cancel(); // Performance monitoring function measurePerformance(name, func) { return function(...args) { const start = performance.now(); const result = func.apply(this, args); const end = performance.now(); console.log(`${name} took ${(end - start).toFixed(2)}ms`); return result; }; } // Combine with debounce/throttle const expensiveOperation = measurePerformance('Expensive Op', () => { // Simulate expensive operation let sum = 0; for (let i = 0; i < 1000000; i++) { sum += i; } return sum; }); const debouncedExpensive = debounce(expensiveOperation, 300); const throttledExpensive = throttle(expensiveOperation, 1000);
When to Use Each
javascript// Use DEBOUNCE for: // - Search input (wait for user to stop typing) // - Form validation // - Auto-save functionality // - API calls triggered by user input // - Window resize calculations // Use THROTTLE for: // - Scroll event handlers // - Mouse move events // - Animation frames // - Progress updates // - Real-time data updates // Example comparison function demonstrateDifference() { let debounceCount = 0; let throttleCount = 0; const debouncedFn = debounce(() => { debounceCount++; console.log('Debounced execution:', debounceCount); }, 1000); const throttledFn = throttle(() => { throttleCount++; console.log('Throttled execution:', throttleCount); }, 1000); // Simulate rapid calls for (let i = 0; i < 10; i++) { setTimeout(() => { debouncedFn(); // Will execute only once (after last call + delay) throttledFn(); // Will execute immediately, then once per second }, i * 100); } } // demonstrateDifference();