Performance & Memoryhigh

Debounce & Throttle

Debounce delays execution until after N ms have passed since the last invocation — use for search input, resize handlers. Throttle limits execution to at most once every N ms — use for scroll handlers, rate-limited API calls.

Memory anchor

Debounce = elevator doors: they keep resetting the close timer every time someone walks in. Throttle = a subway train: it leaves every 5 minutes no matter how many people are waiting on the platform.

Expected depth

Debounce implementation: store a timer ID; on each call, clear the previous timer and set a new one. The callback only fires if the timer completes without interruption. Leading-edge debounce fires immediately on first call, then ignores until quiet period — better UX for button clicks. Throttle implementation: track last execution time; only invoke if the elapsed time exceeds the interval. requestAnimationFrame throttle is a common pattern for scroll handlers: call rAF once per scroll event, update once per frame, cancel pending rAF before registering a new one.

Deep — senior internals

Lodash's debounce has a sophisticated implementation with leading/trailing edge options, maxWait (ensures at least one call within a window), and cancellation. The core challenge: debounce/throttle must preserve this binding and arguments — modern implementations use rest parameters and closures. For React hooks, useCallback(debounce(...), []) is an anti-pattern because debounce creates a new closure each render, losing timer state — the correct approach is useRef to store the debounced function across renders or useMemo with an empty dep array. For TypeScript, preserving the function signature with generics is important: function debounce<T extends (...args: unknown[]) => unknown>(fn: T, ms: number): (...args: Parameters<T>) => void.

🎤Interview-ready answer

Debounce collapses multiple rapid calls into one, firing after the quiet period — ideal for input events where you only care about the final value. Throttle limits call rate to once per interval — ideal for scroll/resize where you want regular updates but can't handle every event. Both prevent performance bottlenecks by reducing work. Can you implement debounce? — store a timer, clearTimeout on each call, setTimeout for the callback, return the wrapper function.

Common trap

In React, creating a debounced function inside a functional component without useRef/useMemo creates a new debounce closure every render, resetting the timer state. This means the debounce never actually delays — every re-render gives you a fresh debounce with no pending timer. Store debounced functions in useRef.