JavaScript Debounce vs Throttle — Runnable Examples & When to Use | TryJS

Learn the difference between debounce and throttle in JavaScript with runnable examples. When to use each, and how to implement both from scratch.

Overview

Debounce and throttle are two ways to control how often a function runs when events fire rapidly — keystrokes, scroll, resize, mousemove. Debounce waits until events stop: the function runs once after a quiet period. Throttle caps the rate: the function runs at most once every N milliseconds, regardless of how many events fire. Choosing the right one depends on whether you care about the final state (debounce) or sampled updates along the way (throttle).

How It Works

Debounce: run after events stop

Every call resets a timer. The function only fires when the timer elapses without being reset. Result: one call after the user stops typing, scrolling, or resizing.

Throttle: cap to one call per interval

The function runs immediately, then ignores subsequent calls until the interval has passed. Result: regular samples at a fixed rate, regardless of event frequency.

Leading vs trailing edges

Both patterns can be configured to fire at the start of a burst (leading), the end (trailing), or both. Most libraries default debounce to trailing and throttle to leading+trailing.

Cleanup matters

Both implementations schedule timers. If a component unmounts or the listener is removed, cancel any pending timer to avoid calling a stale function with stale state.

Common Mistakes

When to Use It

Use debounce for 'final state' operations: autocomplete search, form validation after typing stops, saving drafts. Use throttle for 'live feedback' operations: scroll position reporting, mousemove handlers, resize layout updates.

Runnable Example

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

function throttle(fn, interval) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall >= interval) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

const log = label => value => console.log(label, value);
const debounced = debounce(log("debounced:"), 300);
const throttled = throttle(log("throttled:"), 300);

// Simulate rapid events
[0, 50, 100, 150, 200, 500, 900].forEach((ms, i) => {
  setTimeout(() => {
    debounced(i);
    throttled(i);
  }, ms);
});

Open this example in the TryJS playground to edit and run the code instantly in your browser — no signup needed.

Frequently Asked Questions

What is the difference between debounce and throttle?

Debounce fires once after events stop for a set duration. Throttle fires at most once per interval, regardless of how many events arrive. Debounce cares about the final event; throttle cares about sampling at a fixed rate.

When should I use debounce?

Use debounce when you only care about the final value after a burst — search-as-you-type (wait until typing stops before hitting the API), resize handlers that recompute layout once the window settles, autosave drafts after idle.

When should I use throttle?

Use throttle when you need regular sampled updates during a burst — scroll-position indicators, drag handlers, mousemove tracking, rate-limited API calls where you must send at most N requests per second.

Do I need a library like lodash for debounce and throttle?

No — both can be written in ~10 lines, as shown in the runnable example. Libraries add options (leading/trailing edges, maxWait, cancel methods) that are nice but not essential for simple cases.