Suggest an editImprove this articleRefine the answer for “Throttle in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`throttle` is a wrapper that guarantees a function runs no more often than once per a given number of milliseconds, no matter how many times the event fires.** The first call goes through immediately, its timestamp is remembered, and every later call is ignored until `delay` has elapsed. That is exactly what events firing hundreds of times per second need: `mousemove`, `scroll`, `resize`. Unlike debounce, throttle does not wait for the burst to end; it lets one call through per interval, so a progress bar or a cursor position updates while the action is happening rather than after it. The ready-made Lodash version additionally controls the call at the start and the end of an interval through the `leading` and `trailing` options. ```javascript function throttle(fn, delay) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; fn.apply(this, args); } }; } ``` **Key point:** throttle means "no more often than once per N ms" and gives regular updates during the event, while debounce means "no earlier than N ms after the last event" and gives a single call at the end.Shown above the full answer for quick recall.Answer (EN)Image**`throttle` is a technique that limits how often a function is called: even when an event happens very frequently, the function runs no more often than once per a given time interval.** In plain words the rule is: "call the function no more often than once every N milliseconds". ## Theory ### TL;DR - The throttle rule: "no more often than once per N ms", whatever the number of events. - The implementation keeps the timestamp of the last call (`lastCall`) and lets a new one through only once the interval has passed. - The first call goes through immediately, the following ones are ignored until the delay window closes. - Scenarios: `scroll`, `mousemove`, `resize`, infinite scroll, progress bar updates. - Throttle means "no more often than"; debounce means "no earlier than". The first updates during the action, the second after it. - The Lodash version adds the `leading` and `trailing` options, a call at the start and at the end of the interval. ### Quick example ```javascript function throttle(fn, delay) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; fn.apply(this, args); } }; } const handleMove = throttle(() => { console.log('Mouse move handled'); }, 500); window.addEventListener('mousemove', handleMove); ``` ### The problem: a handler without throttle Suppose you want to track how the user moves the mouse: ```javascript window.addEventListener('mousemove', () => { console.log('mouse move'); }); ``` What goes wrong: - `mousemove` can fire **dozens of times per second** (up to 100-200 times per second); - the function is called far too often; - CPU load grows and performance drops. If the handler reads geometry (`getBoundingClientRect`) or changes styles, every extra firing also triggers another layout pass, so the page starts to jank. ### How throttle works internally ```javascript function throttle(fn, delay) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; fn.apply(this, args); } }; } const handleMove = throttle(() => { console.log('Mouse move handled'); }, 500); window.addEventListener('mousemove', handleMove); ``` Now `handleMove` is called **no more often than once per 500 ms**, even if `mousemove` fires hundreds of times. Step by step: 1. On the first call the function runs immediately. 2. The timestamp of the last call is stored (`lastCall`). 3. All subsequent calls are **ignored** until `delay` milliseconds have passed. 4. Once the time window closes, the function can be called again. Note that this simplest implementation has leading behaviour only. The last event of a burst that lands inside the interval is lost, because there is no trailing call here. ### Examples from real code 1. **Updating a progress bar on scroll:** ```javascript window.addEventListener( 'scroll', throttle(() => { progressBar.value = window.scrollY; }, 100) ); ``` 2. **Tracking the mouse position for an animation:** ```javascript window.addEventListener( 'mousemove', throttle(updateCursorPosition, 50) ); ``` 3. **Recalculating window sizes (`resize`):** ```javascript window.addEventListener( 'resize', throttle(updateLayout, 200) ); ``` Many projects take the ready-made variant from Lodash: ```javascript import { throttle } from 'lodash'; const throttled = throttle(() => { console.log('scroll!'); }, 300); window.addEventListener('scroll', throttled); ``` The Lodash version is more advanced: it can call the function **at the start and/or at the end** of the interval through the `leading` and `trailing` options, and it has a `cancel()` method to drop a pending call. ### The difference from debounce | Feature | **Throttle** | **Debounce** | | --- | --- | --- | | Goal | Limit the call frequency | Postpone execution until the user has finished | | Fires | Regularly, but no more often than once per N ms | Only once after a pause | | Example | `scroll`, `mousemove`, `resize` | `input`, `search`, `autocomplete` | | Behaviour | "No more often than" | "No earlier than" | Concretely: - **Throttle**: while the page scrolls, update the position every 100 ms. - **Debounce**: wait until the user stops typing, and only then send the request. Summary: | Item | Description | | --- | --- | | What it does | Limits how often a function is called | | When to use it | For frequent events: `scroll`, `resize`, `mousemove` | | Arguments | A function and a delay (ms) | | Returns | A new wrapped function | | Benefit | Reduces CPU load and improves performance | | Difference from debounce | Runs regularly, but no more often than the given interval | ### Common mistakes - Creating the wrapper on every firing: `addEventListener('scroll', throttle(fn, 100))` inside another handler or a render produces a new function with `lastCall` at zero each time, so the limit never applies. - Forgetting the lost trailing call: in the naive implementation the last event of a burst is not handled, so the final scroll position may never be recorded. Use Lodash for that, or add a `setTimeout` at the tail of the interval. - Confusing throttle with debounce: for a live search, throttle sends a request every N ms while the user is still typing, when what you want is one request after the pause. - Using throttle for animation instead of `requestAnimationFrame`: for visual updates, rAF is synchronised with browser frames and gives a smoother result. - Using `Date.now()` where monotonic time is required: the system clock can be adjusted, so `performance.now()` is safer for precise intervals. - Not removing the listener when a component unmounts: throttle does not cancel the subscription, and the wrapper keeps driving code for a screen that no longer exists.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.