JavaScript Performance Optimization
Debounce — Implement from Scratch
function debounce(fn, delay) {
let timer
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// Usage: search input autocomplete const search = debounce((query) => fetchResults(query), 300) input.addEventListener('input', e => search(e.target.value))
Fires ONCE after user stops typing for 300ms.
Throttle — Implement from Scratch
function throttle(fn, interval) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= interval) {
lastTime = now
fn.apply(this, args)
}
}
}
// Usage: scroll event handler window.addEventListener('scroll', throttle(updateNav, 100))
Fires at most once per 100ms regardless of scroll speed.
Memory Leaks
Forgotten intervals:
const id = setInterval(() => update(), 1000) // When done: clearInterval(id)
Detached DOM nodes:
let ref = document.createElement('div') document.body.appendChild(ref) document.body.removeChild(ref) // ref still holds the node in memory! ref = null // fix: release the reference
Closures over large objects:
function leaky() { const bigData = new Array(1_000_000).fill('*') return () => bigData[0] // keeps ALL of bigData alive! } function fixed() { const bigData = new Array(1_000_000).fill('*') const first = bigData[0] return () => first // only keeps 'first' }
Reflow vs Repaint
Repaint: visual change only (color). Cheaper. Reflow: geometry change (width, height, position). Cascades — expensive.
// ❌ Layout thrashing (alternating read/write = N reflows)
for (let i = 0; i < 100; i++) {
const h = el.offsetHeight // READ — forces layout
el.style.height = h + 1 + 'px' // WRITE — invalidates layout
}
// ✅ Batch reads then writes (1 reflow) const h = el.offsetHeight for (let i = 0; i < 100; i++) { el.style.height = (h + i) + 'px' }
// ✅ Best: CSS transforms — composited on GPU (no reflow at all) el.style.transform = 'translateY(10px)'
Practice performance questions at [JSPrep Pro](/auth).