Debouncing and Throttling in JavaScript

Debouncing and Throttling - Made Simple!

Think of these as traffic controllers for your functions:

  • Debouncing = Wait until user stops, then act
  • Throttling = Act regularly, but not too often

    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Debouncing and Throttling Demo</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                max-width: 1000px;
                margin: 0 auto;
                padding: 20px;
                background-color: #f5f5f5;
            }

            .header {
                text-align: center;
                margin-bottom: 30px;
            }

            .demo-container {
                background: white;
                border-radius: 10px;
                padding: 25px;
                margin: 20px 0;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            }

            h2 {
                color: #333;
                border-bottom: 3px solid #4CAF50;
                padding-bottom: 10px;
                margin-bottom: 20px;
            }

            .example-box {
                border: 2px solid #e0e0e0;
                border-radius: 8px;
                padding: 20px;
                margin: 15px 0;
                background: #fafafa;
            }

            .input-demo {
                margin: 20px 0;
            }

            input[type="text"] {
                width: 100%;
                padding: 12px;
                font-size: 16px;
                border: 2px solid #ddd;
                border-radius: 5px;
                box-sizing: border-box;
            }

            input[type="text"]:focus {
                outline: none;
                border-color: #4CAF50;
            }

            .counter-display {
                display: flex;
                gap: 30px;
                margin: 20px 0;
                justify-content: space-around;
            }

            .counter-box {
                flex: 1;
                text-align: center;
                padding: 20px;
                border-radius: 8px;
                background: #f5f5f5;
                border: 2px solid #ddd;
            }

            .counter-box.normal {
                border-color: #FF5722;
                background: #FFF3E0;
            }

            .counter-box.debounced {
                border-color: #2196F3;
                background: #E3F2FD;
            }

            .counter-box.throttled {
                border-color: #9C27B0;
                background: #F3E5F5;
            }

            .counter-value {
                font-size: 36px;
                font-weight: bold;
                margin: 10px 0;
            }

            .counter-label {
                font-size: 18px;
                font-weight: bold;
            }

            .visual-demo {
                margin: 30px 0;
            }

            .timeline {
                position: relative;
                height: 60px;
                background: #f0f0f0;
                border-radius: 5px;
                margin: 20px 0;
                overflow: hidden;
            }

            .timeline-event {
                position: absolute;
                width: 3px;
                height: 100%;
                background: #FF5722;
                transition: left 0.1s;
            }

            .timeline-event.executed {
                background: #4CAF50;
                width: 5px;
            }

            button {
                background: #4CAF50;
                color: white;
                border: none;
                padding: 12px 24px;
                border-radius: 5px;
                cursor: pointer;
                font-size: 16px;
                margin: 10px 5px;
                transition: background 0.3s;
            }

            button:hover {
                background: #45a049;
            }

            button:active {
                transform: scale(0.98);
            }

            .code-example {
                background: #263238;
                color: #ECEFF1;
                padding: 20px;
                border-radius: 8px;
                margin: 15px 0;
                font-family: 'Courier New', monospace;
                overflow-x: auto;
                line-height: 1.6;
            }

            .keyword {
                color: #82B1FF;
            }

            .function {
                color: #C3E88D;
            }

            .string {
                color: #F07178;
            }

            .number {
                color: #F78C6C;
            }

            .comment {
                color: #546E7A;
                font-style: italic;
            }

            .comparison-grid {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 20px;
                margin: 20px 0;
            }

            .concept-box {
                background: white;
                padding: 20px;
                border-radius: 8px;
                border: 2px solid #e0e0e0;
            }

            .concept-box h3 {
                margin-top: 0;
                color: #333;
            }

            .real-example {
                background: #E8F5E9;
                padding: 15px;
                border-radius: 5px;
                margin: 10px 0;
                border-left: 4px solid #4CAF50;
            }

            .scroll-demo {
                height: 200px;
                overflow-y: scroll;
                border: 2px solid #ddd;
                border-radius: 5px;
                padding: 20px;
                background: white;
                margin: 20px 0;
            }

            .scroll-content {
                height: 1000px;
                background: linear-gradient(to bottom, #E3F2FD, #F3E5F5);
                padding: 20px;
                position: relative;
            }

            .mouse-area {
                height: 200px;
                background: #f0f0f0;
                border: 2px dashed #999;
                border-radius: 8px;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: crosshair;
                margin: 20px 0;
                position: relative;
            }

            .mouse-tracker {
                position: absolute;
                padding: 5px 10px;
                background: #333;
                color: white;
                border-radius: 5px;
                font-size: 12px;
                pointer-events: none;
            }

            .event-log {
                background: #f5f5f5;
                border: 1px solid #ddd;
                border-radius: 5px;
                padding: 10px;
                height: 150px;
                overflow-y: auto;
                font-family: monospace;
                font-size: 14px;
                margin: 10px 0;
            }

            .log-entry {
                padding: 2px 0;
            }

            .log-entry.normal {
                color: #FF5722;
            }

            .log-entry.debounced {
                color: #2196F3;
            }

            .log-entry.throttled {
                color: #9C27B0;
            }

            .visual-explanation {
                background: #FFF3E0;
                padding: 20px;
                border-radius: 8px;
                margin: 20px 0;
                border: 2px solid #FFB74D;
            }

            .diagram {
                text-align: center;
                margin: 20px 0;
                font-family: monospace;
                font-size: 14px;
                line-height: 1.8;
            }
        </style>
    </head>

    <body>
        <div class="header">
            <h1>🎯 Debouncing vs Throttling</h1>
            <p style="font-size: 18px; color: #666;">Control how often your functions run!</p>
        </div>

        <!-- Concept Introduction -->
        <div class="demo-container">
            <h2>📚 What Are They?</h2>

            <div class="comparison-grid">
                <div class="concept-box">
                    <h3>🛑 Debouncing</h3>
                    <p><strong>Wait until user stops, then execute</strong></p>
                    <div class="real-example">
                        <strong>Real Life:</strong> Elevator doors - they wait until everyone stops entering before closing
                    </div>
                    <p>Perfect for: Search boxes, form validation, save drafts</p>
                </div>

                <div class="concept-box">
                    <h3>⏱️ Throttling</h3>
                    <p><strong>Execute regularly, but limit frequency</strong></p>
                    <div class="real-example">
                        <strong>Real Life:</strong> Subway trains - they leave every 5 minutes, no matter what
                    </div>
                    <p>Perfect for: Scroll events, resize events, API rate limiting</p>
                </div>
            </div>
        </div>

        <!-- Interactive Demo 1: Search Input -->
        <div class="demo-container">
            <h2>🔍 Demo 1: Search Input</h2>
            <p>Type in the search box and see how each approach handles it:</p>

            <div class="input-demo">
                <input type="text" id="searchInput" placeholder="Type to search..." />

                <div class="counter-display">
                    <div class="counter-box normal">
                        <div class="counter-label">❌ Normal</div>
                        <div class="counter-value" id="normalCount">0</div>
                        <div>API calls</div>
                    </div>

                    <div class="counter-box debounced">
                        <div class="counter-label">✅ Debounced (500ms)</div>
                        <div class="counter-value" id="debouncedCount">0</div>
                        <div>API calls</div>
                    </div>

                    <div class="counter-box throttled">
                        <div class="counter-label">✅ Throttled (1s)</div>
                        <div class="counter-value" id="throttledCount">0</div>
                        <div>API calls</div>
                    </div>
                </div>

                <div class="event-log" id="searchLog"></div>
            </div>
        </div>

        <!-- Visual Explanation -->
        <div class="demo-container">
            <h2>👁️ Visual Difference</h2>

            <div class="visual-explanation">
                <h3>Debouncing Timeline</h3>
                <div class="diagram">
                    User types: H E L L O (stops) |--500ms--| → Execute!
                    ↓ ↓ ↓ ↓ ↓ ↑
                    X X X X X ✓
                    (cancelled) (runs)
                </div>
            </div>

            <div class="visual-explanation" style="background: #F3E5F5; border-color: #BA68C8;">
                <h3>Throttling Timeline</h3>
                <div class="diagram">
                    User types: H E L L O W O R L D
                    ↓ ↓ ↓ ↓ ↓
                    ✓ X ✓ X ✓
                    |--1s--| |--1s--| |--1s--|
                    (runs) (runs) (runs)
                </div>
            </div>
        </div>

        <!-- Button Click Demo -->
        <div class="demo-container">
            <h2>🖱️ Demo 2: Button Spam Protection</h2>
            <p>Click the buttons rapidly to see the difference:</p>

            <div class="example-box">
                <button onclick="handleNormalClick()">Normal Button</button>
                <button onclick="handleDebouncedClick()">Debounced Button (2s)</button>
                <button onclick="handleThrottledClick()">Throttled Button (1s)</button>

                <div class="counter-display" style="margin-top: 20px;">
                    <div class="counter-box normal">
                        <div class="counter-label">Normal Clicks</div>
                        <div class="counter-value" id="normalClickCount">0</div>
                    </div>

                    <div class="counter-box debounced">
                        <div class="counter-label">Debounced Actions</div>
                        <div class="counter-value" id="debouncedClickCount">0</div>
                    </div>

                    <div class="counter-box throttled">
                        <div class="counter-label">Throttled Actions</div>
                        <div class="counter-value" id="throttledClickCount">0</div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Mouse Move Demo -->
        <div class="demo-container">
            <h2>🖱️ Demo 3: Mouse Tracking</h2>
            <p>Move your mouse in the area below:</p>

            <div class="mouse-area" id="mouseArea">
                <span style="color: #999;">Move mouse here</span>
                <div class="mouse-tracker" id="mouseTracker" style="display: none;"></div>
            </div>

            <div class="counter-display">
                <div class="counter-box normal">
                    <div class="counter-label">Normal Events</div>
                    <div class="counter-value" id="mouseMoveCount">0</div>
                </div>

                <div class="counter-box throttled">
                    <div class="counter-label">Throttled (100ms)</div>
                    <div class="counter-value" id="mouseThrottledCount">0</div>
                </div>
            </div>
        </div>

        <!-- Code Examples -->
        <div class="demo-container">
            <h2>📝 Simple Implementation</h2>

            <h3>Debounce Function</h3>
            <div class="code-example">
                <span class="comment">// Simple Debounce Implementation</span>
                <span class="keyword">function</span> <span class="function">debounce</span>(func, delay) {
                <span class="keyword">let</span> timeoutId;

                <span class="keyword">return</span> <span class="keyword">function</span>(...args) {
                <span class="comment">// Clear previous timeout if user is still typing</span>
                clearTimeout(timeoutId);

                <span class="comment">// Set new timeout</span>
                timeoutId = setTimeout(() => {
                func.apply(<span class="keyword">this</span>, args);
                }, delay);
                };
                }

                <span class="comment">// Usage Example</span>
                <span class="keyword">const</span> search = <span class="function">debounce</span>((text) => {
                console.log(<span class="string">'Searching for:'</span>, text);
                }, <span class="number">500</span>);

                <span class="comment">// This will only execute once, 500ms after last call</span>
                search(<span class="string">'H'</span>);
                search(<span class="string">'He'</span>);
                search(<span class="string">'Hello'</span>); <span class="comment">// Only this executes!</span>
            </div>

            <h3>Throttle Function</h3>
            <div class="code-example">
                <span class="comment">// Simple Throttle Implementation</span>
                <span class="keyword">function</span> <span class="function">throttle</span>(func, limit) {
                <span class="keyword">let</span> inThrottle;

                <span class="keyword">return</span> <span class="keyword">function</span>(...args) {
                <span class="keyword">if</span> (!inThrottle) {
                func.apply(<span class="keyword">this</span>, args);
                inThrottle = <span class="keyword">true</span>;

                setTimeout(() => {
                inThrottle = <span class="keyword">false</span>;
                }, limit);
                }
                };
                }

                <span class="comment">// Usage Example</span>
                <span class="keyword">const</span> updatePosition = <span class="function">throttle</span>((x, y) => {
                console.log(<span class="string">'Position:'</span>, x, y);
                }, <span class="number">1000</span>);

                <span class="comment">// Only executes once per second maximum</span>
                updatePosition(<span class="number">10</span>, <span class="number">20</span>); <span class="comment">//
                    Executes</span>
                updatePosition(<span class="number">15</span>, <span class="number">25</span>); <span class="comment">//
                    Skipped</span>
                updatePosition(<span class="number">20</span>, <span class="number">30</span>); <span class="comment">//
                    Skipped</span>
            </div>
        </div>

        <!-- Real World Examples -->
        <div class="demo-container">
            <h2>🌍 Real World Examples</h2>

            <div class="comparison-grid">
                <div class="concept-box">
                    <h3>Debouncing Use Cases</h3>
                    <ul>
                        <li><strong>Search Box:</strong> Wait until user stops typing</li>
                        <li><strong>Auto-save:</strong> Save after user stops editing</li>
                        <li><strong>Form Validation:</strong> Validate after user finishes</li>
                        <li><strong>Window Resize:</strong> Recalculate after resize ends</li>
                    </ul>
                </div>

                <div class="concept-box">
                    <h3>Throttling Use Cases</h3>
                    <ul>
                        <li><strong>Scroll Events:</strong> Check position every 100ms</li>
                        <li><strong>API Rate Limiting:</strong> Max 1 request per second</li>
                        <li><strong>Game Movements:</strong> Update position at fixed rate</li>
                        <li><strong>Progress Updates:</strong> Update UI periodically</li>
                    </ul>
                </div>
            </div>
        </div>

        <!-- When to Use What -->
        <div class="demo-container">
            <h2>🤔 Which One to Use?</h2>

            <table style="width: 100%; border-collapse: collapse;">
                <tr>
                    <th style="border: 1px solid #ddd; padding: 12px; background: #f2f2f2;">Scenario</th>
                    <th style="border: 1px solid #ddd; padding: 12px; background: #f2f2f2;">Use Debounce</th>
                    <th style="border: 1px solid #ddd; padding: 12px; background: #f2f2f2;">Use Throttle</th>
                </tr>
                <tr>
                    <td style="border: 1px solid #ddd; padding: 12px;">Search suggestions</td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                </tr>
                <tr>
                    <td style="border: 1px solid #ddd; padding: 12px;">Infinite scroll</td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                </tr>
                <tr>
                    <td style="border: 1px solid #ddd; padding: 12px;">Button click (prevent spam)</td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                </tr>
                <tr>
                    <td style="border: 1px solid #ddd; padding: 12px;">Mouse position tracking</td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                    <td style="border: 1px solid #ddd; padding: 12px; text-align: center;"></td>
                </tr>
            </table>
        </div>

        <script>
            // Counters
            let normalCount = 0;
            let debouncedCount = 0;
            let throttledCount = 0;
            let normalClickCount = 0;
            let debouncedClickCount = 0;
            let throttledClickCount = 0;
            let mouseMoveCount = 0;
            let mouseThrottledCount = 0;

            // Debounce implementation
            function debounce(func, delay) {
                let timeoutId;
                return function (...args) {
                    clearTimeout(timeoutId);
                    timeoutId = setTimeout(() => {
                        func.apply(this, args);
                    }, delay);
                };
            }

            // Throttle implementation
            function throttle(func, limit) {
                let inThrottle;
                return function (...args) {
                    if (!inThrottle) {
                        func.apply(this, args);
                        inThrottle = true;
                        setTimeout(() => {
                            inThrottle = false;
                        }, limit);
                    }
                };
            }

            // Search input handlers
            const searchInput = document.getElementById('searchInput');
            const searchLog = document.getElementById('searchLog');

            function addLogEntry(text, type = 'normal') {
                const entry = document.createElement('div');
                entry.className = `log-entry ${type}`;
                entry.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
                searchLog.insertBefore(entry, searchLog.firstChild);

                // Keep only last 5 entries
                while (searchLog.children.length > 5) {
                    searchLog.removeChild(searchLog.lastChild);
                }
            }

            // Normal search
            function normalSearch(value) {
                normalCount++;
                document.getElementById('normalCount').textContent = normalCount;
                addLogEntry(`Normal: Searching "${value}"`, 'normal');
            }

            // Debounced search
            const debouncedSearch = debounce((value) => {
                debouncedCount++;
                document.getElementById('debouncedCount').textContent = debouncedCount;
                addLogEntry(`Debounced: Searching "${value}"`, 'debounced');
            }, 500);

            // Throttled search
            const throttledSearch = throttle((value) => {
                throttledCount++;
                document.getElementById('throttledCount').textContent = throttledCount;
                addLogEntry(`Throttled: Searching "${value}"`, 'throttled');
            }, 1000);

            // Search input event
            searchInput.addEventListener('input', (e) => {
                const value = e.target.value;
                normalSearch(value);
                debouncedSearch(value);
                throttledSearch(value);
            });

            // Button click handlers
            function handleNormalClick() {
                normalClickCount++;
                document.getElementById('normalClickCount').textContent = normalClickCount;
            }

            const handleDebouncedClick = debounce(() => {
                debouncedClickCount++;
                document.getElementById('debouncedClickCount').textContent = debouncedClickCount;
            }, 2000);

            const handleThrottledClick = throttle(() => {
                throttledClickCount++;
                document.getElementById('throttledClickCount').textContent = throttledClickCount;
            }, 1000);

            // Mouse tracking
            const mouseArea = document.getElementById('mouseArea');
            const mouseTracker = document.getElementById('mouseTracker');

            function updateMousePosition(e) {
                const rect = mouseArea.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;

                mouseMoveCount++;
                document.getElementById('mouseMoveCount').textContent = mouseMoveCount;

                mouseTracker.style.display = 'block';
                mouseTracker.style.left = x + 'px';
                mouseTracker.style.top = y + 'px';
                mouseTracker.textContent = `${Math.round(x)}, ${Math.round(y)}`;
            }

            const throttledMouseMove = throttle((e) => {
                mouseThrottledCount++;
                document.getElementById('mouseThrottledCount').textContent = mouseThrottledCount;
            }, 100);

            mouseArea.addEventListener('mousemove', (e) => {
                updateMousePosition(e);
                throttledMouseMove(e);
            });

            mouseArea.addEventListener('mouseleave', () => {
                mouseTracker.style.display = 'none';
            });
        </script>
    </body>

    </html>

Simple Explanation

🛑 Debouncing - "Wait until calm"

Imagine you're in an elevator. The door doesn't close every time someone presses the button. It waits until everyone stops pressing, THEN closes.


    // Without debounce - runs immediately every time
    input.addEventListener('input', function (e) {
        console.log('Searching...'); // Fires on EVERY keystroke!
    });

    // With debounce - waits until typing stops
    const debouncedSearch = debounce(function (e) {
        console.log('Searching...'); // Fires 500ms AFTER last keystroke
    }, 500);

    input.addEventListener('input', debouncedSearch);

⏱️ Throttling - "Regular intervals"

Like a bus that leaves every 10 minutes. It doesn't matter if 1 or 100 people are waiting - it leaves on schedule.


    // Without throttle - runs on every scroll event
    window.addEventListener('scroll', function () {
        console.log('Scrolling...'); // Could fire 100+ times per second!
    });

    // With throttle - runs at most once per second
    const throttledScroll = throttle(function () {
        console.log('Scrolling...'); // Fires at most once per second
    }, 1000);

    window.addEventListener('scroll', throttledScroll);

Simple Implementation

Debounce - Step by Step


    function debounce(func, wait) {
        let timeout;

        return function executedFunction(...args) {
            // Clear the previous timer
            clearTimeout(timeout);

            // Set a new timer
            timeout = setTimeout(() => {
                func(...args);
            }, wait);
        };
    }

Throttle - Step by Step


    function throttle(func, limit) {
        let inThrottle;

        return function (...args) {
            if (!inThrottle) {
                func.apply(this, args);
                inThrottle = true;

                setTimeout(() => {
                    inThrottle = false;
                }, limit);
            }
        };
    }

Real Examples You Use Daily

1. Search Box (Debouncing)


    const searchInput = document.getElementById('search');

    // Bad - Makes API call on every keystroke
    searchInput.addEventListener('input', (e) => {
        fetch(`/api/search?q=${e.target.value}`); // Too many requests!
    });

    // Good - Waits until user stops typing
    const search = debounce((e) => {
        fetch(`/api/search?q=${e.target.value}`); // Much better!
    }, 500);

    searchInput.addEventListener('input', search);

2. Scroll Position (Throttling)


    // Bad - Checks on every pixel scrolled
    window.addEventListener('scroll', () => {
        if (window.scrollY > 1000) {
            showBackToTop(); // Called hundreds of times!
        }
    });

    // Good - Checks every 200ms maximum
    const checkScroll = throttle(() => {
        if (window.scrollY > 1000) {
            showBackToTop(); // Called sensibly
        }
    }, 200);

    window.addEventListener('scroll', checkScroll);

3. Button Click Protection (Debouncing)


    const submitBtn = document.getElementById('submit');

    // Prevent accidental double-clicks
    const handleSubmit = debounce(() => {
        submitForm(); // Only submits once even if clicked multiple times
    }, 2000);

    submitBtn.addEventListener('click', handleSubmit);

4. Window Resize (Debouncing)


    // Bad - Recalculates on every pixel change
    window.addEventListener('resize', () => {
        recalculateLayout(); // Very expensive!
    });

    // Good - Waits until resize is done
    const handleResize = debounce(() => {
        recalculateLayout(); // Runs once after resizing stops
    }, 300);

    window.addEventListener('resize', handleResize);

When to Use Which?

Use Debouncing When:

  • Waiting for user to finish: Search, typing, form validation
  • Expensive operations: API calls, complex calculations
  • Final result matters: Save draft, submit form

Use Throttling When:

  • Regular updates needed: Progress bars, scroll position
  • Continuous events: Mouse move, scroll, resize
  • Rate limiting: API calls, animations

Common Mistakes

Mistake 1: Using Debounce for Scroll


    // ❌ Wrong - User scrolls continuously, function may never run!
    const handleScroll = debounce(() => {
        loadMoreContent();
    }, 1000);

    // ✅ Right - Use throttle for continuous events
    const handleScroll = throttle(() => {
        loadMoreContent();
    }, 1000);

Mistake 2: Using Throttle for Search


    // ❌ Wrong - Might search while user is still typing
    const handleSearch = throttle((e) => {
        searchAPI(e.target.value);
    }, 500);

    // ✅ Right - Wait until user stops typing
    const handleSearch = debounce((e) => {
        searchAPI(e.target.value);
    }, 500);

Quick Reference

Feature

Debouncing

Throttling

When it runs

After delay from last call

At regular intervals

Use for

User input, final actions

Continuous events

Example

Search box

Scroll events

Analogy

Elevator door

Bus schedule

One-Line Summary

  • Debouncing: "I'll wait until you're completely done"
  • Throttling: "I'll do it regularly, but not too often"

No comments:

Post a Comment

Debouncing and Throttling in JavaScript

Debouncing and Throttling - Made Simple! Think of these as traffic controllers for your functions: Debouncing = Wait until user stops, ...