Skip to Content
Course content

38: Practice Exercise: Building a Debounce and Throttle Utility

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a pull request for a junior developer who had built a "Live Search" feature for a massive product catalog. On his local machine with five test products, it worked beautifully. But when we pushed it to a staging environment with 50,000 items, the site practically crawled to a halt. Why? Because every single keystroke in the search box was triggering a network request. If a user typed "Wireless Headphones" quickly, we were hitting the API 18 times in three seconds. The server was gasping for air, and the UI was flickering as responses returned out of order. We didn't need a faster server; we needed to stop the client from being so chatty.

Controlling the Noise with Debouncing

When you're dealing with events that fire rapidly—like keyup or input—you often don't care about every single event. You only care when the user has stopped doing the action. This is where debouncing comes in. Think of it like an elevator door: it doesn't close the second you step in; it waits for a few seconds of silence before finally shutting. If someone else steps in while it's waiting, the timer resets.

In JavaScript, we achieve this using closures and setTimeout. You create a wrapper function that manages a timer. Every time the wrapped function is called, you clear the previous timer and start a new one. The actual logic only executes if the timer manages to reach zero without being interrupted. It's a simple pattern, but it's a lifesaver for search bars or window resizing logic where you only need the final result.

function debounce(func, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

Setting a Steady Rhythm with Throttling

Throttling is a different beast entirely. While debouncing waits for a pause, throttling ensures a function is called at most once every X milliseconds. I like to think of it as a faucet with a drip limiter. No matter how hard you turn the handle, you're only getting one drop per second.

You'll find this indispensable for things like scroll or mousemove events. If you're calculating an element's position on the screen as the user scrolls, running that code 60 times per second is usually overkill and can lead to "jank" (those annoying micro-stutters in the UI). Throttling lets you say, "I'll update the position every 100ms," which is plenty fast for the human eye but drastically lighter on the CPU.

The implementation usually involves tracking the last time the function was executed. If the difference between "now" and the "last execution time" is greater than your limit, you let the function run and update the timestamp. Otherwise, you just ignore the call. It's less about waiting for silence and more about enforcing a speed limit.

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



📋 Practical Task

Exercise: Building a High-Performance Event Controller

You are tasked with optimizing a page that has a search input and a scroll-based animation. Currently, both are firing too often and killing performance. Your goal is to implement your own debounce and throttle utilities from scratch to fix this.

Requirements:

  • Implement a debounce(fn, delay) function that ensures the provided function fn is only called after delay milliseconds have passed since the last time it was invoked.
  • Implement a throttle(fn, limit) function that ensures the provided function fn is called at most once every limit milliseconds.
  • Test your debounce function by attaching it to a text input: the console should only log "Searching for..." once the user has stopped typing for 500ms.
  • Test your throttle function by attaching it to the window.onscroll event: the console should log "Scroll position updated" at most once every 200ms, regardless of how fast the user scrolls.

Starter Code:

const searchInput = document.querySelector('#search');
const handleSearch = (e) => console.log('Searching for:', e.target.value);

const handleScroll = () => console.log('Scroll position updated');

// Your debounce and throttle implementations here...

// Apply them:
// searchInput.addEventListener('input', debounce(handleSearch, 500));
// window.addEventListener('scroll', throttle(handleScroll, 200));
Rating
0 0

There are no comments for now.

to be the first to leave a comment.