-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
38: Practice Exercise: Building a Debounce and Throttle Utility
A few years ago, I was reviewing a pull request for a junior developer who had built a "Live Search" feature for a massive product catalog. On his local machine with five test products, it worked beautifully. But when we pushed it to a staging environment with 50,000 items, the site practically crawled to a halt. Why? Because every single keystroke in the search box was triggering a network request. If a user typed "Wireless Headphones" quickly, we were hitting the API 18 times in three seconds. The server was gasping for air, and the UI was flickering as responses returned out of order. We didn't need a faster server; we needed to stop the client from being so chatty.
Controlling the Noise with Debouncing
When you're dealing with events that fire rapidly—like keyup or input—you often don't care about every single event. You only care when the user has stopped doing the action. This is where debouncing comes in. Think of it like an elevator door: it doesn't close the second you step in; it waits for a few seconds of silence before finally shutting. If someone else steps in while it's waiting, the timer resets.
In JavaScript, we achieve this using closures and setTimeout. You create a wrapper function that manages a timer. Every time the wrapped function is called, you clear the previous timer and start a new one. The actual logic only executes if the timer manages to reach zero without being interrupted. It's a simple pattern, but it's a lifesaver for search bars or window resizing logic where you only need the final result.
function debounce(func, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
Setting a Steady Rhythm with Throttling
Throttling is a different beast entirely. While debouncing waits for a pause, throttling ensures a function is called at most once every X milliseconds. I like to think of it as a faucet with a drip limiter. No matter how hard you turn the handle, you're only getting one drop per second.
You'll find this indispensable for things like scroll or mousemove events. If you're calculating an element's position on the screen as the user scrolls, running that code 60 times per second is usually overkill and can lead to "jank" (those annoying micro-stutters in the UI). Throttling lets you say, "I'll update the position every 100ms," which is plenty fast for the human eye but drastically lighter on the CPU.
The implementation usually involves tracking the last time the function was executed. If the difference between "now" and the "last execution time" is greater than your limit, you let the function run and update the timestamp. Otherwise, you just ignore the call. It's less about waiting for silence and more about enforcing a speed limit.
function throttle(func, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}📋 Practical Task
Exercise: Building a High-Performance Event Controller
You are tasked with optimizing a page that has a search input and a scroll-based animation. Currently, both are firing too often and killing performance. Your goal is to implement your own debounce and throttle utilities from scratch to fix this.
Requirements:
- Implement a
debounce(fn, delay)function that ensures the provided functionfnis only called afterdelaymilliseconds have passed since the last time it was invoked. - Implement a
throttle(fn, limit)function that ensures the provided functionfnis called at most once everylimitmilliseconds. - Test your
debouncefunction by attaching it to a text input: the console should only log "Searching for..." once the user has stopped typing for 500ms. - Test your
throttlefunction by attaching it to thewindow.onscrollevent: the console should log "Scroll position updated" at most once every 200ms, regardless of how fast the user scrolls.
Starter Code:
const searchInput = document.querySelector('#search');
const handleSearch = (e) => console.log('Searching for:', e.target.value);
const handleScroll = () => console.log('Scroll position updated');
// Your debounce and throttle implementations here...
// Apply them:
// searchInput.addEventListener('input', debounce(handleSearch, 500));
// window.addEventListener('scroll', throttle(handleScroll, 200));
There are no comments for now.