CrackFrontendCF
Resources
Practice
CrackFrontend

Your guide to mastering frontend interviews

Resources

LibraryPracticeDonate

Company

About UsContact

Legal

Privacy PolicyTerms of Service

Built with ❤️ by Tushar Khanna

CrackFrontendCF
Resources
Practice

⏱️ Throttle Function in JavaScript

Limits function execution to once per time period. Critical for scroll tracking, resize handling, and API rate limiting.

Interview Importance: 🔴 Critical — Often asked alongside debounce. Understanding the difference between throttle and debounce is essential for any frontend interview. -- 1️⃣ What is Throttle? Throttle ensures a function is called at most once in a specified time period. Unlike debounce (which waits for silence), throttle guarantees regular execution during continuous events. Real-World Analogy Think of a machine gun with a rate limiter: It can only fire once per second, no matter how fast you pull the trigger Even if you hold the trigger continuously, it fires at regular intervals -- 2️⃣ Why Use Throttle? Common Use Cases Problem Without Throttle Hundreds of events per scroll Layout calculations on every pixel Tracking every pixel movement Overwhelming server with requests Inconsistent frame rates Performance Comparison -- 3️⃣ How Throttle Works — Implementation Basic Implementation (Leading Edge) 🔍 Dry Run: Step-by-Step Execution -- 4️⃣ Understanding Throttle Variants Leading Edge (Default Above) Executes immediately on first call, then waits: Trailing Edge Executes at the end of the wait period: Both Leading and Trailing -- 5️⃣ Complete Production Implementation Usage Examples -- 6️⃣ React Implementations useThrottle Hook useThrottledValue Hook -- 7️⃣ Throttle vs Debounce Throttle At regular intervals Max N calls per second Scroll, resize, rate limiting Limits frequency Visual Comparison When to Use Which? Use Debounce Throttle Throttle Debounce Throttle Debounce Throttle Throttle -- 8️⃣ Common Interview Questions Q1: Implement a throttle function Answer: See Section 3 for basic, Section 5 for production. Q2: What's the difference between throttle and debounce? Answer: Throttle: Ensures function runs at most once per interval (rate limiting) Debounce: Ensures function runs only after a pause (grouping) Q3: When would you use leading vs trailing edge? Answer: Leading: When you want immediate response (button clicks, first scroll) Trailing: When you want the final state (form validation, final scroll position) Both: When you want immediate final (tracking with guaranteed last value) Q4: How would you implement throttle with requestAnimationFrame? Answer: Q5: How do you test a throttled function? Answer: -- 9️⃣ Common Pitfalls Pitfall 1: Creating New Throttled Function Each Render Pitfall 2: Losing Context Pitfall 3: Not Cleaning Up Trailing Timer Pitfall 4: Using Wrong Delay -- 🔟 Time & Space Complexity Complexity O(1) O(1) O(1) -- Summary Description Limits execution to once per time period Leading edge Execute at end of period Use cases Regular intervals vs waiting for silence | Key Takeaways 1. Throttle limits frequency — ideal for continuous events 2. Know leading vs trailing — understand when each fires 3. Clean up timers — cancel on unmount 4. Use requestAnimationFrame — for visual updates (~60fps) 5. Choose the right tool — throttle for limiting, debounce for grouping -- 📚 Further Reading Lodash throttle documentation CSS-Tricks: Debouncing and Throttling Explained MDN: requestAnimationFrame -- <!-quiz-start --Q1: If you throttle a function with a 100ms delay and call it 50 times in 1 second, approximately how many times will it execute? [ ] 50 times [x] 10 times [ ] 1 time [ ] 100 times Q2: What is the difference between leading edge and trailing edge throttle? [ ] Leading is faster than trailing [x] Leading executes immediately on first call; trailing executes at end of delay [ ] Leading only works with scroll events [ ] There is no practical difference Q3: Which scenario is BEST suited for throttle (not debounce)? [ ] Search input autocomplete [ ] Form field validation after typing [x] Tracking scroll position during scrolling [ ] Auto-saving after user stops editing <!-quiz-end --
JavaScriptUtilities
➕ Chained Sum (Curried Function)
medium
⏱️ Debounce Function in JavaScript
medium
📋 Deep Clone Implementation
easy
🔄 distinctUntilChanged() Polyfill
easy
📄 Document Comparison (Diff)
easy
📢 Custom EventEmitter Implementation
hard
📦 Flatten Object Implementation
medium
🐫➡️🐍 Converting camelCase to snake_case in JavaScript (Without Regex)
easy
🔄 mapLimit: Controlled Concurrency in JavaScript
medium
⚡️ Fire on Push: Dispatching Custom Events When an Array Changes in JavaScript
medium
🔄 Removing Circular References from Objects
hard
📊 Sampling Function: Execute Once Every N Calls
medium
⏱️ Throttle Function in JavaScript
medium
🔄 undefinedToNull Utility
medium
13 of 14
LibraryJavaScriptUtilities60 of 61

⏱️ Throttle Function in JavaScript

jsutilsmedium

Interview Importance: 🔴 Critical — Often asked alongside debounce. Understanding the difference between throttle and debounce is essential for any frontend interview.


1️⃣ What is Throttle?

Throttle ensures a function is called at most once in a specified time period. Unlike debounce (which waits for silence), throttle guarantees regular execution during continuous events.

Real-World Analogy

Think of a machine gun with a rate limiter:

  • It can only fire once per second, no matter how fast you pull the trigger
  • Even if you hold the trigger continuously, it fires at regular intervals
Without Throttle:           With Throttle (100ms):
------------------          ----------------------
Scroll event at 0ms  -> fn   Scroll event at 0ms   -> fn ✓
Scroll event at 10ms -> fn   Scroll event at 10ms  -> (ignored)
Scroll event at 20ms -> fn   Scroll event at 20ms  -> (ignored)
Scroll event at 30ms -> fn   ...
...                         Scroll event at 100ms -> fn ✓
Scroll event at 100ms -> fn  Scroll event at 110ms -> (ignored)
                            ...
100 calls in 1 second!      10 calls in 1 second!

2️⃣ Why Use Throttle?

Common Use Cases

Use CaseProblem Without ThrottleSolution With Throttle
Scroll trackingHundreds of events per scrollUpdate at fixed intervals
Resize handlingLayout calculations on every pixelCalculate every N ms
Mouse moveTracking every pixel movementSample position periodically
API rate limitingOverwhelming server with requestsLimit request frequency
Game loopInconsistent frame ratesFixed update intervals

Performance Comparison

// Scrolling for 2 seconds at ~60 events/second:
// Without throttle: ~120 function calls
// With 100ms throttle: ~20 function calls (83% reduction)

3️⃣ How Throttle Works — Implementation

Basic Implementation (Leading Edge)

function throttle(fn, delay) {
  let lastCall = 0;

  return function(...args) {
    const now = Date.now();

    if (now - lastCall >= delay) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

🔍 Dry Run: Step-by-Step Execution

const log = (msg) => console.log(msg, Date.now());
const throttledLog = throttle(log, 1000);

// Rapid calls:
throttledLog("A");  // t = 0ms
throttledLog("B");  // t = 100ms
throttledLog("C");  // t = 200ms
throttledLog("D");  // t = 1000ms
throttledLog("E");  // t = 1100ms
Time      | Action                    | lastCall | Condition           | Result
----------|---------------------------|----------|---------------------|--------
0ms       | throttledLog("A")         | 0        | 0 - 0 >= 1000?      |
          |                           |          | = 0 >= 1000? No     |
          | But lastCall starts at 0, |          | First call special: |
          | so 0 - 0 = 0 >= 1000?     |          | Actually, yes!*     | Execute!
          | lastCall = 0              | 0        |                     | log("A")
          |                           |          |                     |
100ms     | throttledLog("B")         | 0        | 100 - 0 >= 1000?    |
          |                           |          | = 100 >= 1000? No   | Ignored
          |                           |          |                     |
200ms     | throttledLog("C")         | 0        | 200 - 0 >= 1000?    |
          |                           |          | = 200 >= 1000? No   | Ignored
          |                           |          |                     |
1000ms    | throttledLog("D")         | 0        | 1000 - 0 >= 1000?   |
          |                           |          | = 1000 >= 1000? Yes | Execute!
          | lastCall = 1000           | 1000     |                     | log("D")
          |                           |          |                     |
1100ms    | throttledLog("E")         | 1000     | 1100 - 1000 >= 1000?|
          |                           |          | = 100 >= 1000? No   | Ignored

* Note: First call passes because lastCall=0 and Date.now() returns
  a timestamp like 1702345678000, so the difference is huge.

Output: "A" at ~0ms, "D" at ~1000ms

4️⃣ Understanding Throttle Variants

Leading Edge (Default Above)

Executes immediately on first call, then waits:

Events:    --●--●--●--●--●--●----------●--●--●--
           0                500ms            1000ms

Execution: --●--------------●----------●--------
           0                500ms      1000ms
           (immediate)      (after 500ms delay)

Trailing Edge

Executes at the end of the wait period:

function throttleTrailing(fn, delay) {
  let lastCall = 0;
  let timerId = null;

  return function(...args) {
    const now = Date.now();
    const remaining = delay - (now - lastCall);

    if (remaining <= 0) {
      clearTimeout(timerId);
      lastCall = now;
      fn.apply(this, args);
    } else if (!timerId) {
      timerId = setTimeout(() => {
        lastCall = Date.now();
        timerId = null;
        fn.apply(this, args);
      }, remaining);
    }
  };
}

Both Leading and Trailing

function throttle(fn, delay, options = { leading: true, trailing: true }) {
  let lastCall = 0;
  let timerId = null;
  let lastArgs = null;

  const invoke = (time) => {
    lastCall = time;
    fn.apply(null, lastArgs);
    lastArgs = null;
  };

  return function(...args) {
    const now = Date.now();
    const remaining = delay - (now - lastCall);
    lastArgs = args;

    if (remaining <= 0) {
      // Time for a new call
      clearTimeout(timerId);
      timerId = null;

      if (options.leading) {
        invoke(now);
      }
    } else if (!timerId && options.trailing) {
      // Schedule trailing call
      timerId = setTimeout(() => {
        invoke(options.leading ? Date.now() : 0);
        timerId = null;
      }, remaining);
    }
  };
}

5️⃣ Complete Production Implementation

function throttle(fn, delay, options = {}) {
  const { leading = true, trailing = true } = options;

  let lastCall = 0;
  let timerId = null;
  let lastArgs = null;
  let lastThis = null;

  function invokeFunction(time) {
    lastCall = time;
    fn.apply(lastThis, lastArgs);
    lastArgs = null;
    lastThis = null;
  }

  function throttled(...args) {
    const now = Date.now();
    const remaining = delay - (now - lastCall);

    lastArgs = args;
    lastThis = this;

    // First call or enough time has passed
    if (remaining <= 0 || remaining > delay) {
      if (timerId) {
        clearTimeout(timerId);
        timerId = null;
      }
      if (leading) {
        invokeFunction(now);
      }
    } else if (!timerId && trailing) {
      // Schedule trailing edge call
      timerId = setTimeout(() => {
        invokeFunction(leading ? Date.now() : 0);
        timerId = null;
      }, remaining);
    }
  }

  // Cancel any pending execution
  throttled.cancel = function() {
    clearTimeout(timerId);
    timerId = null;
    lastArgs = null;
    lastThis = null;
    lastCall = 0;
  };

  return throttled;
}

Usage Examples

// Default: leading + trailing
const throttled1 = throttle(handleScroll, 100);

// Leading only: execute immediately, ignore trailing
const throttled2 = throttle(handleScroll, 100, { trailing: false });

// Trailing only: wait until end of period
const throttled3 = throttle(handleScroll, 100, { leading: false });

6️⃣ React Implementations

useThrottle Hook

import { useRef, useCallback, useEffect } from 'react';

function useThrottle(callback, delay) {
  const lastCallRef = useRef(0);
  const callbackRef = useRef(callback);

  // Keep callback ref updated
  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  return useCallback((...args) => {
    const now = Date.now();

    if (now - lastCallRef.current >= delay) {
      lastCallRef.current = now;
      callbackRef.current(...args);
    }
  }, [delay]);
}

// Usage:
function ScrollTracker() {
  const throttledScroll = useThrottle((e) => {
    console.log('Scroll position:', window.scrollY);
  }, 100);

  useEffect(() => {
    window.addEventListener('scroll', throttledScroll);
    return () => window.removeEventListener('scroll', throttledScroll);
  }, [throttledScroll]);

  return <div style={{ height: '200vh' }}>Scroll me</div>;
}

useThrottledValue Hook

import { useState, useEffect, useRef } from 'react';

function useThrottledValue(value, delay) {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastUpdateRef = useRef(Date.now());

  useEffect(() => {
    const now = Date.now();
    const timeSinceLastUpdate = now - lastUpdateRef.current;

    if (timeSinceLastUpdate >= delay) {
      lastUpdateRef.current = now;
      setThrottledValue(value);
    } else {
      const timerId = setTimeout(() => {
        lastUpdateRef.current = Date.now();
        setThrottledValue(value);
      }, delay - timeSinceLastUpdate);

      return () => clearTimeout(timerId);
    }
  }, [value, delay]);

  return throttledValue;
}

// Usage:
function MouseTracker() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const throttledPosition = useThrottledValue(position, 100);

  useEffect(() => {
    const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);

  return <div>Position: {throttledPosition.x}, {throttledPosition.y}</div>;
}

7️⃣ Throttle vs Debounce

AspectThrottleDebounce
When it firesAt regular intervalsAfter a pause in calls
GuaranteesMax N calls per secondOnly after silence
Use caseScroll, resize, rate limitingSearch input, auto-save
BehaviorLimits frequencyWaits for inactivity

Visual Comparison

Events:     --●--●●●●●------●●●------●----------●--------
            0  50        500       700        900ms

Throttle (200ms):
            --●----●----●----●----●----●----●----●------
            0   200  400  500  700  800  900 1000ms
            (fires at regular 200ms intervals during activity)

Debounce (200ms):
            -----------------●-------------●------------●
                            700ms        1100ms      (after)
            (fires 200ms after each pause)

When to Use Which?

ScenarioUseWhy
Search inputDebounceWait for user to stop typing
Scroll positionThrottleNeed regular updates
Window resizeThrottleCalculate layout periodically
Auto-saveDebounceSave after user pauses
Button spamThrottleAllow first click, limit rate
Form validationDebounceValidate after input stops
Infinite scrollThrottleCheck position regularly
API rate limitThrottleMax N requests per second

8️⃣ Common Interview Questions

Q1: Implement a throttle function

Answer: See Section 3 for basic, Section 5 for production.

Q2: What's the difference between throttle and debounce?

Answer:

  • Throttle: Ensures function runs at most once per interval (rate limiting)
  • Debounce: Ensures function runs only after a pause (grouping)
// Continuous scrolling for 1 second:
// Throttle (100ms): ~10 calls (every 100ms)
// Debounce (100ms): 1 call (100ms after scrolling stops)

Q3: When would you use leading vs trailing edge?

Answer:

  • Leading: When you want immediate response (button clicks, first scroll)
  • Trailing: When you want the final state (form validation, final scroll position)
  • Both: When you want immediate + final (tracking with guaranteed last value)

Q4: How would you implement throttle with requestAnimationFrame?

Answer:

function throttleRAF(fn) {
  let scheduled = false;

  return function(...args) {
    if (!scheduled) {
      scheduled = true;
      requestAnimationFrame(() => {
        fn.apply(this, args);
        scheduled = false;
      });
    }
  };
}

// Throttles to ~60fps (16.67ms between calls)
// Best for visual updates (animations, scroll effects)

Q5: How do you test a throttled function?

Answer:

jest.useFakeTimers();

test('throttle limits call frequency', () => {
  const fn = jest.fn();
  const throttled = throttle(fn, 100);

  // Rapid calls
  throttled('a');  // Should execute (first call)
  throttled('b');  // Should be throttled
  throttled('c');  // Should be throttled

  expect(fn).toHaveBeenCalledTimes(1);
  expect(fn).toHaveBeenCalledWith('a');

  // Advance time
  jest.advanceTimersByTime(100);

  throttled('d');  // Should execute (100ms passed)
  expect(fn).toHaveBeenCalledTimes(2);
  expect(fn).toHaveBeenLastCalledWith('d');
});

9️⃣ Common Pitfalls

Pitfall 1: Creating New Throttled Function Each Render

// ❌ BAD: New throttled function each render
function ScrollHandler() {
  const handleScroll = throttle(() => {
    console.log('scroll');
  }, 100);  // Created fresh each render!

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);  // Changes every render!
}

// ✅ GOOD: Stable reference with useMemo
function ScrollHandler() {
  const handleScroll = useMemo(
    () => throttle(() => console.log('scroll'), 100),
    []
  );

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
      handleScroll.cancel?.();
    };
  }, [handleScroll]);
}

Pitfall 2: Losing this Context

// ❌ BAD: Arrow function in throttle loses this
const throttled = throttle(() => {
  console.log(this.value);  // this is wrong!
}, 100);

// ✅ GOOD: Use regular function
const throttled = throttle(function() {
  console.log(this.value);  // this is correct
}, 100);

// ✅ ALSO GOOD: Bind explicitly
const throttled = throttle(this.handler.bind(this), 100);

Pitfall 3: Not Cleaning Up Trailing Timer

// ❌ BAD: Timer might fire after component unmounts
useEffect(() => {
  const throttled = throttle(updateState, 100);
  window.addEventListener('scroll', throttled);
  return () => window.removeEventListener('scroll', throttled);
  // Trailing timer might still fire!
}, []);

// ✅ GOOD: Cancel on cleanup
useEffect(() => {
  const throttled = throttle(updateState, 100);
  window.addEventListener('scroll', throttled);
  return () => {
    window.removeEventListener('scroll', throttled);
    throttled.cancel();  // Cancel pending trailing call
  };
}, []);

Pitfall 4: Using Wrong Delay

// ❌ BAD: Too short delay defeats purpose
const throttled = throttle(expensiveOperation, 10);  // Only saves 10ms

// ❌ BAD: Too long delay causes poor UX
const throttled = throttle(updatePosition, 500);  // Feels laggy

// ✅ GOOD: Balance performance and UX
const throttled = throttle(updatePosition, 16);   // ~60fps
const throttled = throttle(apiCall, 100);         // Reasonable rate limit

🔟 Time & Space Complexity

AspectComplexityExplanation
Throttled callO(1)Just checks time and maybe calls fn
SpaceO(1)Stores lastCall, timerId, lastArgs
setTimeoutO(1)Browser handles scheduling

Summary

ConceptDescription
ThrottleLimits execution to once per time period
Leading edgeExecute immediately on first call
Trailing edgeExecute at end of period
Use casesScroll, resize, rate limiting
Key difference from debounceRegular intervals vs waiting for silence

Key Takeaways

  1. Throttle limits frequency — ideal for continuous events
  2. Know leading vs trailing — understand when each fires
  3. Clean up timers — cancel on unmount
  4. Use requestAnimationFrame — for visual updates (~60fps)
  5. Choose the right tool — throttle for limiting, debounce for grouping

📚 Further Reading

  • Lodash throttle documentation
  • CSS-Tricks: Debouncing and Throttling Explained
  • MDN: requestAnimationFrame

Quick Quiz

Test your understanding with 3 quick questions

Q1If you throttle a function with a 100ms delay and call it 50 times in 1 second, approximately how many times will it execute?
Q2What is the difference between leading edge and trailing edge throttle?
Q3Which scenario is BEST suited for throttle (not debounce)?

Continue Reading

CrackFrontend

Your guide to mastering frontend interviews

Resources

LibraryPracticeDonate

Company

About UsContact

Legal

Privacy PolicyTerms of Service

Built with ❤️ by Tushar Khanna