JavaScript Performance Optimization Explained — Debounce, Throttle & Memory Leaks
JavaScript performance optimization is the practice of writing code that runs efficiently, uses memory responsibly, and keeps the user interface responsive. A slow app frustrates users and drains battery. The Doda Browser optimizes every render cycle, and the Durga Antivirus Pro dashboard uses Web Workers to offload threat analysis so the UI never freezes during scans.
What You’ll Learn
- Event delegation — handling events efficiently on many elements
- Debouncing and throttling — controlling how often functions run
- Avoiding layout thrashing — batching DOM reads and writes
requestAnimationFrame— smooth, efficient animations- Web Workers for CPU-heavy computation
- Detecting and fixing memory leaks
- Building a throttle scroll handler
Why Performance Matters
Users expect web apps to be instant. A study by Google found that 53% of mobile users abandon sites that take longer than 3 seconds to load. Beyond load time, runtime performance matters — janky scrolling, frozen inputs, and stuttering animations make an app feel broken. Performance isn’t a feature; it’s a requirement.
Learning Path
flowchart LR
A[JS DOM & Browser APIs] --> B[Web APIs]
B --> C[Performance Optimization]
C --> D[Closures & Scope]
C --> E[You Are Here]
Event Delegation
Instead of attaching an event listener to every child element, attach one to a parent and use event bubbling:
// ❌ Inefficient — one listener per button
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', handleDelete);
});
// ✅ Efficient — one listener on the parent
document.querySelector('.list-container').addEventListener('click', (event) => {
const button = event.target.closest('.delete-btn');
if (!button) return; // Not a delete button
const itemId = button.dataset.id;
console.log('Deleting item:', itemId);
});Why it’s faster: Event delegation uses less memory (one listener instead of hundreds), works for dynamically added elements, and reduces setup time. The Doda Browser uses event delegation for its bookmark list, which can contain thousands of entries.
Debouncing
Debouncing delays a function until after a specified quiet period. It’s ideal for search inputs:
function debounce(fn, delay = 300) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
// Usage — search input
const searchInput = document.getElementById('search');
const handleSearch = debounce(async (event) => {
const query = event.target.value;
if (query.length < 2) return;
const results = await fetch(`/api/search?q=${query}`);
console.log('Search results:', await results.json());
}, 400);
searchInput.addEventListener('input', handleSearch);When to use: Search inputs, auto-save, form validation on input, window resize handlers.
Analogy: Debouncing is like waiting for someone to stop typing before asking them a question. You wait for a pause, then respond.
Throttling
Throttling limits a function to run at most once per specified interval. It’s ideal for scroll handlers:
function throttle(fn, limit = 100) {
let inThrottle = false;
let lastArgs = null;
return function(...args) {
if (inThrottle) {
lastArgs = args; // Remember latest args
return;
}
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
if (lastArgs) {
fn.apply(this, lastArgs);
lastArgs = null;
}
}, limit);
};
}
// Usage — scroll handler
const handleScroll = throttle(() => {
const scrollPos = window.scrollY;
console.log('Scroll position:', scrollPos);
}, 200);
window.addEventListener('scroll', handleScroll);When to use: Scroll events, resize events, mousemove tracking, progress updates.
Debounce vs Throttle
| Aspect | Debounce | Throttle |
|---|---|---|
| Runs after | A pause in calls | Every N milliseconds |
| Guaranteed execution | Only if there’s a pause | Yes, at regular intervals |
| Best for | Search, auto-save | Scroll, resize, animation |
Avoiding Layout Thrashing
Layout thrashing happens when you repeatedly read and write to the DOM, forcing the browser to recalculate layout over and over:
// ❌ Bad — causes layout thrashing
for (let i = 0; i < 100; i++) {
const width = element.offsetWidth; // Read (triggers layout)
element.style.width = `${width + 1}px`; // Write (triggers layout again)
}
// ✅ Good — batch reads, then batch writes
const widths = [];
for (let i = 0; i < elements.length; i++) {
widths.push(elements[i].offsetWidth); // Read all
}
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = `${widths[i] + 1}px`; // Write all
}Properties that trigger layout (avoid reading these in loops): offsetWidth, offsetHeight, clientWidth, clientHeight, getBoundingClientRect(), scrollTop, scrollLeft.
requestAnimationFrame
Use requestAnimationFrame for visual updates instead of setTimeout:
// ❌ setTimeout — not synchronized with display refresh
function animate() {
element.style.transform = `translateX(${x}px)`;
x += 1;
setTimeout(animate, 16); // ~60fps
}
// ✅ requestAnimationFrame — synchronized with vsync
function animate() {
element.style.transform = `translateX(${x}px)`;
x += 1;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);Why it’s better: requestAnimationFrame pauses when the tab is hidden (saving battery), matches the display refresh rate, and batches all DOM writes before the next paint.
Web Workers for Heavy Computation
Move CPU-intensive work off the main thread so the UI stays responsive:
// main.js
const worker = new Worker('compute-worker.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (event) => {
console.log('Computation result:', event.data);
updateUI(event.data);
};
// compute-worker.js
self.onmessage = (event) => {
const { data } = event.data;
// Heavy computation — doesn't block the UI
const result = heavyProcessing(data);
self.postMessage(result);
};Real-world use: The Durga Antivirus Pro dashboard uses Web Workers to analyze file signatures, compare hashes against threat databases, and pattern-match malware signatures — all without freezing the user interface.
Memory Leaks
Memory leaks happen when JavaScript holds references to objects that should be garbage-collected:
// Leak 1: Global variables
function createLeak() {
leaked = 'I am global'; // Implicit global — never cleaned up
}
// Leak 2: Forgotten event listeners
class Component {
constructor(element) {
this.handler = () => console.log('Clicked');
element.addEventListener('click', this.handler);
// ❌ Never remove the listener
}
destroy() {
// ✅ Always clean up
this.element?.removeEventListener('click', this.handler);
}
}
// Leak 3: Closures holding large objects
function createLeak() {
const largeData = new Array(1000000).fill('x');
return () => {
console.log(largeData.length); // Holds reference forever
};
}
// Leak 4: Detached DOM nodes
const elements = [];
for (let i = 0; i < 1000; i++) {
const el = document.createElement('div');
document.body.appendChild(el);
elements.push(el);
}
document.body.innerHTML = ''; // Elements still referenced in array!
How to Detect Memory Leaks
- Chrome DevTools → Performance tab — Record activity and look for growing memory.
- Chrome DevTools → Memory tab — Take heap snapshots and compare.
performance.memory(Chrome-only) — CheckusedJSHeapSizeover time.
Common Mistakes
1. Not debouncing search inputs
input.addEventListener('input', () => fetchSearchResults(query));
// Fires on every keystroke — hundreds of API calls
Fix: Debounce by 300-400ms.
2. Using setTimeout for animations
setTimeout(animate, 16); // Rough 60fps, but not synced with display
Fix: Use requestAnimationFrame.
3. Reading layout properties in loops
for (const el of elements) {
const w = el.offsetWidth; // Forces layout on every iteration
el.style.width = `${w + 1}px`;
}Fix: Batch reads and writes separately.
4. Never cleaning up event listeners
Fix: Always save the handler reference and call removeEventListener.
5. Blocking the main thread with heavy computation
const result = heavyComputation(largeData); // Freezes the UI
Fix: Use Web Workers.
Practice Questions
What’s the difference between debounce and throttle? Debounce delays execution until after a pause. Throttle limits execution to once per interval. Debounce is for search inputs, throttle is for scroll events.
What causes layout thrashing? Alternating DOM reads and writes in a loop, forcing the browser to recalculate layout repeatedly.
Why is
requestAnimationFramebetter thansetTimeoutfor animations? It syncs with the display refresh rate, pauses when the tab is hidden, and batches DOM writes.How do you detect a memory leak in JavaScript? Use Chrome DevTools Memory tab — take heap snapshots before and after an operation and compare.
Challenge: Build a utility function that combines debounce and throttle — executes immediately but won’t fire again within the limit (throttledDebounce).
Mini Project: Optimized Scroll Handler
class ScrollManager {
constructor(options = {}) {
this.throttleMs = options.throttleMs || 100;
this.onScroll = options.onScroll || (() => {});
this.onStop = options.onStop || (() => {});
this._setup();
}
_setup() {
let lastCall = 0;
let stopTimeout;
window.addEventListener('scroll', () => {
const now = Date.now();
// Throttle
if (now - lastCall >= this.throttleMs) {
lastCall = now;
this.onScroll({
x: window.scrollX,
y: window.scrollY
});
}
// Debounce — detect when scrolling stops
clearTimeout(stopTimeout);
stopTimeout = setTimeout(() => {
this.onStop({
x: window.scrollX,
y: window.scrollY
});
}, 150);
}, { passive: true });
}
destroy() {
// Clean up if needed
}
}
const scroller = new ScrollManager({
onScroll: (pos) => {
// Update UI during scroll (throttled)
updateProgressBar(pos.y);
},
onStop: (pos) => {
// Load more content when scrolling stops
console.log('Scroll stopped at:', pos.y);
loadMoreContentIfNeeded(pos.y);
}
});FAQ
What’s Next
| Lesson | Description |
|---|---|
| JavaScript Home | Back to the JavaScript hub |
| https://tutorials.dodatech.com/programming-languages/javascript/js-web-apis/ | Web APIs — workers, observers |
| https://tutorials.dodatech.com/programming-languages/javascript/js-closures-scope/ | Closures & scope deep dive |
| https://tutorials.dodatech.com/programming-languages/javascript/js-dom/ | DOM & Browser APIs |
| V8 Engine | How JavaScript engines optimize code |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
What’s Next
Congratulations on completing this JavaScript Performance Optimization tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro