Explain how garbage collection works in JavaScript. What patterns can lead to memory leaks?
3 minintermediatejavascriptgarbagecollectionworks
Quick Answer
JavaScript uses automatic garbage collection to manage memory, freeing up memory that is no longer referenced by the program.
Detailed Answer
Explain how garbage collection works in JavaScript. What patterns can lead to memory leaks?
Answer: JavaScript uses automatic garbage collection to manage memory, freeing up memory that is no longer referenced by the program.
How Garbage Collection Works:
Mark and Sweep Algorithm:
- Mark Phase: Traverses all reachable objects from root references (global variables, call stack)
- Sweep Phase: Removes all unmarked objects (unreachable)
- Compact Phase: Defragments memory (optional, varies by engine)
Generational Collection:
- Young Generation: New objects, collected frequently
- Old Generation: Long-lived objects, collected less frequently
- Objects that survive multiple collections move to old generation
Memory Leak Patterns:
- Global Variables:
// BAD: Creates global variables
function createUser() {
name = 'John'; // Missing var/let/const
this.email = 'john@example.com'; // this refers to global in non-strict mode
}
// GOOD: Proper variable declaration
function createUser() {
const name = 'John';
const email = 'john@example.com';
return { name, email };
}
- Closures with Large Objects:
// BAD: Keeps large object in memory
function createHandler() {
const largeData = new Array(1000000).fill('data');
return function(event) {
// Handler keeps reference to largeData
console.log('Event handled');
};
}
// GOOD: Clear references when done
function createHandler() {
const largeData = new Array(1000000).fill('data');
const handler = function(event) {
console.log('Event handled');
};
// Provide cleanup method
handler.cleanup = function() {
largeData.length = 0;
};
return handler;
}
- Event Listeners:
// BAD: No cleanup
class Component {
constructor(element) {
this.element = element;
this.element.addEventListener('click', this.handleClick);
}
handleClick = () => {
console.log('Clicked');
}
}
// GOOD: Proper cleanup
class Component {
constructor(element) {
this.element = element;
this.boundHandleClick = this.handleClick.bind(this);
this.element.addEventListener('click', this.boundHandleClick);
}
handleClick() {
console.log('Clicked');
}
destroy() {
this.element.removeEventListener('click', this.boundHandleClick);
this.element = null;
}
}
- Timers and Intervals:
// BAD: No cleanup
class Timer {
constructor() {
this.interval = setInterval(() => {
console.log('Timer tick');
}, 1000);
}
}
// GOOD: Proper cleanup
class Timer {
constructor() {
this.interval = setInterval(() => {
console.log('Timer tick');
}, 1000);
}
destroy() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
}
- DOM References:
// BAD: Keeps DOM references
class Component {
constructor() {
this.elements = document.querySelectorAll('.item');
this.cache = new Map();
}
processItems() {
this.elements.forEach(element => {
// Process elements
this.cache.set(element.id, element.offsetHeight);
});
}
}
// GOOD: Clear references
class Component {
constructor() {
this.elements = document.querySelectorAll('.item');
this.cache = new Map();
}
processItems() {
this.elements.forEach(element => {
this.cache.set(element.id, element.offsetHeight);
});
}
destroy() {
this.elements = null;
this.cache.clear();
}
}
- Circular References:
// BAD: Circular reference
function createCircularRef() {
const objA = { name: 'A' };
const objB = { name: 'B' };
objA.ref = objB;
objB.ref = objA; // Circular reference
return objA;
}
// GOOD: Use WeakMap or WeakSet for weak references
function createWeakRef() {
const objA = { name: 'A' };
const objB = { name: 'B' };
const weakMap = new WeakMap();
weakMap.set(objA, objB);
weakMap.set(objB, objA);
return { objA, objB, weakMap };
}
Memory Leak Detection:
// Monitor memory usage
function logMemoryUsage() {
if (performance.memory) {
const memory = performance.memory;
console.log({
used: Math.round(memory.usedJSHeapSize / 1048576) + ' MB',
total: Math.round(memory.totalJSHeapSize / 1048576) + ' MB',
limit: Math.round(memory.jsHeapSizeLimit / 1048576) + ' MB'
});
}
}
// Use Chrome DevTools
// 1. Open DevTools → Memory tab
// 2. Take heap snapshots
// 3. Compare snapshots to find memory leaks
// 4. Use Performance tab to monitor memory over time
Best Practices:
- Use
let/constinstead ofvar - Remove event listeners when components are destroyed
- Clear timers and intervals
- Use WeakMap/WeakSet for weak references
- Avoid storing large objects in closures
- Use object pooling for frequently created/destroyed objects
- Monitor memory usage in development