- React
- Live Machine Coding Challenges and Custom Hooks
- Machine Coding: useDebounce & useThrottle Custom Hooks
Machine Coding: useDebounce & useThrottle Custom Hooks
useDebounce delays updating a value until after a specified delay has elapsed since the last change, canceling previous timers in useEffect cleanup. useThrottle ensures an action executes at most once per time window using useRef to track timestamps or trailing timers.
Key Learning Objectives
Implement a production-grade useDebounce hook with cleanup cancellation.
Implement a useThrottle hook that prevents rapid execution during scroll or resize events.
Manage timers and cancel pending callbacks on component unmount.
Explain when to debounce user input vs when to throttle high-frequency streams.
The Interview Problem & Context
Implement a reusable `useDebounce<T>(value: T, delay: number): T` hook that delays updating the returned value until `delay` milliseconds have elapsed without changes, and include full timer cleanup.
1import { useState, useEffect, useRef } from 'react';23// 1. useDebounce Value Hook4export function useDebounce<T>(value: T, delay: number): T {5 const [debouncedValue, setDebouncedValue] = useState<T>(value);67 useEffect(() => {8 // Set timer to update state after delay9 const timer = setTimeout(() => {10 setDebouncedValue(value);11 }, delay);1213 // Cancel timer if value or delay changes before timeout fires14 return () => {15 clearTimeout(timer);16 };17 }, [value, delay]);1819 return debouncedValue;20}2122// 2. useThrottle Hook (Leading + Trailing execution)23export function useThrottle<T>(value: T, interval = 500): T {24 const [throttledValue, setThrottledValue] = useState<T>(value);25 const lastExecuted = useRef<number>(Date.now());2627 useEffect(() => {28 const now = Date.now();29 const elapsed = now - lastExecuted.current;3031 if (elapsed >= interval) {32 lastExecuted.current = now;33 setThrottledValue(value);34 } else {35 const timer = setTimeout(() => {36 lastExecuted.current = Date.now();37 setThrottledValue(value);38 }, interval - elapsed);3940 return () => clearTimeout(timer);41 }42 }, [value, interval]);4344 return throttledValue;45}
Deep Architectural Explanation
Debounce vs Throttle: When to Use Which
1. Debouncing (Wait for quiet period):
- Concept: Postpones execution until N milliseconds have passed since the LAST event.
- Use Cases:
- Search auto-complete typeahead (do not fire HTTP request on every keystroke).
- Auto-saving draft forms (wait until user stops typing for 1 second).
- Window resize recalculations (wait until resize drag completes).
2. Throttling (Rate-limiting frequency):
- Concept: Guarantees execution at a regular maximum rate (e.g. at most once every 200ms).
- Use Cases:
- Infinite scroll scroll position tracking.
- Drag-and-drop mouse movement coordinates.
- Rate-limiting button clicks on submit actions.
Mechanics of useDebounce:
- When
valuechanges,useEffectschedulessetTimeout. - If
valuechanges again beforedelaymilliseconds pass, React executes the effect cleanup function, runningclearTimeout(timer). - The previous pending timer is aborted; a new timer begins.
- Only when the user pauses typing does the timer expire, updating
debouncedValueand triggering a single re-render.
Common Interview Traps & Pitfalls
Forgetting the cleanup function in useEffect, leading to multiple queued timers firing out of order.
Re-creating the debounce delay function inside render without useCallback.
Using debounce when throttle is needed (e.g. infinite scroll where user never stops scrolling).
Senior Interviewer Follow-Up Probes
How would you implement useDebouncedCallback for imperative functions (like an analytics logger)?
How do you test useDebounce in Jest using `jest.useFakeTimers()` and `jest.advanceTimersByTime()`?
What are the memory implications of storing large object values in useDebounce?
Frequently Asked Interview Questions
Why does useDebounce return the initial value on mount?
Because debouncedValue state is initialized with the initial value argument, ensuring the component displays valid data without delay on first render.
How do you cancel a pending debounced update when unmounting?
The useEffect cleanup function returns () => clearTimeout(timer), which React calls automatically whenever dependencies change or when the component unmounts.
Can you debounce an event handler instead of a state value?
Yes, using a useDebouncedCallback hook that wraps a function in a ref and returns a debounced invoker.
How do you write unit tests for useDebounce?
Use renderHook from @testing-library/react with jest.useFakeTimers(). Change props with rerender and advance timers with act(() => jest.advanceTimersByTime(500)).
Does useDebounce cause unnecessary re-renders?
No, it reduces re-renders significantly because internal state updates only fire once after the quiet period rather than on every keystroke.
