Problem Statement
Which of the following can cause memory leaks in JavaScript?
Explanation
Memory leaks occur when objects are no longer needed but still referenced, preventing garbage collection.
Common causes include forgotten event listeners, timers not cleared, closures holding large objects, and detached DOM nodes.
Event listeners keep references to elements and callbacks. Always remove listeners when done.
SetTimeout and setInterval must be cleared. Global variables never get garbage collected.
Closures can accidentally hold references to large objects. Be careful what you capture.
Understanding memory leaks is essential for building long-running applications.
Code Solution
SolutionRead Only
// MEMORY LEAK: Forgotten event listeners
function setupButton() {
const button = document.getElementById('btn');
button.addEventListener('click', function handler() {
console.log('Clicked');
});
// Memory leak! Listener never removed
// If setupButton called repeatedly, listeners accumulate
}
// FIX: Remove listener
function setupButtonCorrect() {
const button = document.getElementById('btn');
function handler() {
console.log('Clicked');
}
button.addEventListener('click', handler);
// Clean up
return () => {
button.removeEventListener('click', handler);
};
}
const cleanup = setupButtonCorrect();
// Later: cleanup();
// MEMORY LEAK: Timers not cleared
function startTimer() {
setInterval(() => {
console.log('Running');
}, 1000);
// Memory leak! Timer never stopped
}
// FIX: Clear timer
function startTimerCorrect() {
const timerId = setInterval(() => {
console.log('Running');
}, 1000);
return () => {
clearInterval(timerId);
};
}
const stopTimer = startTimerCorrect();
// Later: stopTimer();
// MEMORY LEAK: Closures holding large data
function createClosure() {
const hugeArray = new Array(1000000).fill('data');
return function() {
// Only uses one element
console.log(hugeArray[0]);
// But holds reference to entire array!
};
}
// FIX: Only capture what you need
function createClosureCorrect() {
const hugeArray = new Array(1000000).fill('data');
const firstElement = hugeArray[0]; // Extract needed data
return function() {
console.log(firstElement);
// Only holds one element, not entire array
};
}
// MEMORY LEAK: Detached DOM nodes
const elements = [];
function createElements() {
const div = document.createElement('div');
document.body.appendChild(div);
elements.push(div); // Store reference
// Remove from DOM
document.body.removeChild(div);
// Memory leak! Still referenced in array
}
// FIX: Remove references
function createElementsCorrect() {
const div = document.createElement('div');
document.body.appendChild(div);
return () => {
document.body.removeChild(div);
// No stored reference, can be garbage collected
};
}
// MEMORY LEAK: Global variables
function processData() {
// Accidental global (no var/let/const)
data = new Array(1000000);
// Never garbage collected!
}
// FIX: Use proper declarations
function processDataCorrect() {
const data = new Array(1000000);
// Garbage collected when function ends
}
// MEMORY LEAK: Circular references (old IE)
function createCircular() {
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
// Modern engines handle this
// But be aware in older browsers
}
// DETECTING MEMORY LEAKS
// Use Chrome DevTools Memory Profiler:
// 1. Take heap snapshot
// 2. Perform action
// 3. Take another snapshot
// 4. Compare snapshots
// 5. Look for growing objects
// Monitor memory programmatically
if (performance.memory) {
console.log('Used:', performance.memory.usedJSHeapSize);
console.log('Total:', performance.memory.totalJSHeapSize);
}
// BEST PRACTICES TO AVOID LEAKS
// 1. Always remove event listeners
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
mount() {
button.addEventListener('click', this.handleClick);
}
unmount() {
button.removeEventListener('click', this.handleClick);
}
handleClick() {
console.log('Clicked');
}
}
// 2. Clear all timers
class Timer {
constructor() {
this.timers = [];
}
setTimeout(callback, delay) {
const id = setTimeout(callback, delay);
this.timers.push(id);
}
cleanup() {
this.timers.forEach(id => clearTimeout(id));
this.timers = [];
}
}
// 3. Use WeakMap/WeakSet for object keys
const cache = new WeakMap();
function processObject(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = expensiveOperation(obj);
cache.set(obj, result);
return result;
}
// When obj is garbage collected, entry removed automatically
// 4. Avoid global variables
// Use modules, IIFEs, or proper scoping
// 5. Be careful with closures
function createHandlers() {
const data = fetchLargeData();
// Bad: captures entire data object
const handler1 = () => console.log(data.field1);
// Good: only captures needed field
const field1 = data.field1;
const handler2 = () => console.log(field1);
return handler2;
}