Skip to main content

JavaScript Debounce & Throttle

In web applications, certain events like scrolling, resizing, and key presses can fire hundreds of times in a short period. This can lead to performance bottlenecks, especially when these events trigger resource-intensive operations. JavaScript's debounce and throttle techniques help manage these events efficiently, improving overall application performance.

Understanding the Problem

Let's start with an example. Imagine a search box that makes API calls as a user types:

javascript
const searchInput = document.getElementById('search-input');

searchInput.addEventListener('input', function(e) {
fetchSearchResults(e.target.value);
});

function fetchSearchResults(query) {
console.log(`Fetching results for: ${query}`);
// API call happens here
}

The problem? If a user types "javascript" quickly, the fetchSearchResults function will be called 10 times (once for each character), creating unnecessary API calls and potentially overwhelming your server.

Debounce: Wait Until Activity Stops

Debouncing ensures that a function is only executed after a certain amount of time has passed without it being called again. Think of it as "executing the function only after the user has stopped typing."

How Debounce Works

  1. User triggers an event (e.g., types a character)
  2. A timer starts
  3. If another event occurs before the timer completes, the timer resets
  4. The function executes only when the timer completes without interruption

Implementing Debounce

Here's a simple debounce implementation:

javascript
function debounce(func, wait) {
let timeout;

return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};

clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}

Using Debounce in Practice

Let's apply this to our search example:

javascript
const searchInput = document.getElementById('search-input');

// Wrap the fetchSearchResults function with debounce
const debouncedFetchSearchResults = debounce(function(query) {
console.log(`Fetching results for: ${query}`);
// API call happens here
}, 500); // Wait 500ms after user stops typing

searchInput.addEventListener('input', function(e) {
debouncedFetchSearchResults(e.target.value);
});

Now, if a user types "javascript" quickly, fetchSearchResults will be called only once, 500ms after they finish typing.

Throttle: Execute at a Regular Interval

Throttling limits how often a function can be called in a given time period. Think of it as "execute this function at most once every X milliseconds."

How Throttle Works

  1. User triggers an event
  2. If it's the first event or if enough time has passed since the last execution, the function executes
  3. Otherwise, the event is ignored
  4. After a specified time period, the function can be executed again

Implementing Throttle

Here's a simple throttle implementation:

javascript
function throttle(func, limit) {
let inThrottle;

return function(...args) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}

Using Throttle in Practice

Throttling is perfect for events that fire continuously, like scrolling or resizing:

javascript
const container = document.getElementById('infinite-scroll-container');

// Create a throttled version of the loadMoreContent function
const throttledLoadMoreContent = throttle(function() {
if (container.scrollTop + container.clientHeight >= container.scrollHeight - 200) {
console.log('Loading more content...');
// Load more content logic here
}
}, 300); // Execute at most once every 300ms

container.addEventListener('scroll', throttledLoadMoreContent);

This ensures that our scroll handler doesn't fire too frequently, preventing performance issues while still providing a smooth user experience.

When to Use Each Technique

Both techniques optimize performance but serve different purposes:

Use Debounce When:

  • You want to execute after activity has stopped (typing, resizing)
  • The final state is what matters (final search term, final window size)
  • Examples: Search inputs, window resizing, form validation

Use Throttle When:

  • You want regular execution during an ongoing activity
  • Intermediate states matter
  • Examples: Scroll events, game input, mousemove events

Real-World Examples

javascript
// HTML: <input id="autocomplete-search" placeholder="Search...">
const searchInput = document.getElementById('autocomplete-search');

const debouncedAutocomplete = debounce(async function(query) {
if (query.length < 2) return;

try {
const response = await fetch(`https://api.example.com/search?q=${query}`);
const results = await response.json();
displayResults(results);
} catch (error) {
console.error('Search failed:', error);
}
}, 350);

searchInput.addEventListener('input', (e) => {
debouncedAutocomplete(e.target.value);
});

Throttle: Window Resize Handler

javascript
// Recalculate layout when window resizes, but not too often
const throttledResizeHandler = throttle(function() {
const newWidth = window.innerWidth;
const newHeight = window.innerHeight;

console.log(`Adjusting layout for new dimensions: ${newWidth}x${newHeight}`);
recalculateLayout(newWidth, newHeight);
}, 250);

window.addEventListener('resize', throttledResizeHandler);

Using Libraries

Instead of implementing debounce and throttle from scratch, you can use established libraries:

With Lodash:

javascript
// Import Lodash in your project
import _ from 'lodash';

// Create a debounced function
const debouncedFunction = _.debounce(myFunction, 300);

// Create a throttled function
const throttledFunction = _.throttle(myFunction, 300);

Advanced Usage: Immediate Execution

Sometimes you want to execute the function immediately on the first call and then debounce subsequent calls. Lodash's debounce supports this with the {leading: true} option:

javascript
const searchWithImmediate = _.debounce(searchFunction, 500, {
leading: true, // Execute on the leading edge (immediately)
trailing: true // Also execute on the trailing edge (after delay)
});

Performance Considerations

  • Choose appropriate delay times: Too short may not solve performance issues, too long affects user experience
  • Clean up event listeners and timers when components unmount to prevent memory leaks
  • Consider using requestAnimationFrame for animation-related throttling

Summary

Debounce and throttle are powerful techniques for optimizing event-driven JavaScript:

  • Debounce waits until activity stops before executing (ideal for typing, resizing)
  • Throttle executes at regular intervals during activity (ideal for scrolling, continuous events)

Implementing these techniques helps create more responsive web applications by preventing excessive function calls, reducing server load, and improving the user experience.

Exercises

  1. Create a debounced search input that displays results only after a user pauses typing
  2. Implement a throttled window scroll handler that loads more content when the user approaches the bottom of the page
  3. Create a resize observer that adjusts a canvas size when the window resizes, but not too frequently
  4. Build a text area that automatically saves content to localStorage, debounced to prevent excessive writes

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)