Skip to content
Plurafy

Memory Management and Garbage Collection

Understanding how JavaScript manages memory

Write memory-efficient code, detect memory leaks, and optimize garbage collection impact.

JavaScript automatically manages memory through garbage collection. Understanding how it works helps write efficient code.

// Objects referenced are kept in memory
let obj = { name: 'John' };
// Unreachable object can be garbage collected
obj = null; // Remove reference
// Objects with circular references are still collectable
const a = { name: 'A' };
const b = { name: 'B' };
a.ref = b;
b.ref = a;
a = null; // When no external reference, both collected
b = null;
// ❌ Event listener leak
function setupButton() {
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
// If btn removed from DOM, listener still holds reference
console.log('clicked');
});
}
// ✅ Fix: Remove listener when not needed
function setupButton() {
const btn = document.getElementById('btn');
const handler = () => console.log('clicked');
btn.addEventListener('click', handler);
btn.addEventListener('remove', () => {
btn.removeEventListener('click', handler);
});
}
// ❌ Accumulating arrays
const listeners = [];
function subscribe(callback) {
listeners.push(callback); // Never cleared!
}
// ✅ Fix: Track and clean up
class EventBus {
constructor() {
this.listeners = [];
}
subscribe(callback) {
this.listeners.push(callback);
// Return unsubscribe function
return () => {
const index = this.listeners.indexOf(callback);
if (index > -1) this.listeners.splice(index, 1);
};
}
}
// Weak references don't prevent garbage collection
const cache = new WeakMap();
class User {
constructor(id) {
this.id = id;
}
}
const user = new User(1);
cache.set(user, { metadata: 'data' });
// When user is garbage collected, cache entry is too
// without preventing garbage collection
// ❌ Memory leak: Removed but still referenced
const detachedNode = document.getElementById('old-element');
document.body.removeChild(detachedNode);
// detachedNode still holds memory!
// ✅ Fix: Clear reference
let detachedNode = document.getElementById('old-element');
document.body.removeChild(detachedNode);
detachedNode = null; // Allow GC
// ❌ Accumulate large objects
const cache = [];
function cacheResponse(data) {
cache.push({ timestamp: Date.now(), data }); // Grows unbounded!
}
// ✅ Fix: Implement limits
class LimitedCache {
constructor(maxSize = 100) {
this.cache = [];
this.maxSize = maxSize;
}
add(item) {
this.cache.push(item);
if (this.cache.length > this.maxSize) {
this.cache.shift(); // Remove oldest
}
}
}
// ❌ Closure retains unnecessary variables
function createHandler(data) {
const largeData = new Array(10000).fill(data);
return () => {
console.log(data); // But closure retains entire largeData!
};
}
// ✅ Fix: Only reference what's needed
function createHandler(data) {
return () => {
console.log(data); // Only data is retained
};
}
// ✅ Or explicitly clear
function createHandler(data) {
let largeData = new Array(10000).fill(data);
const handler = () => console.log(data);
largeData = null; // Clear
return handler;
}
// Check memory usage (Chrome DevTools)
if (performance.memory) {
console.log('Used:', performance.memory.usedJSHeapSize);
console.log('Limit:', performance.memory.jsHeapSizeLimit);
const percentUsed =
(performance.memory.usedJSHeapSize /
performance.memory.jsHeapSizeLimit * 100).toFixed(2);
console.log(`Heap ${percentUsed}% full`);
}
// 1. Minimize global variables
// ❌ const global = { data: largeObject };
// ✅ Use modules
// 2. Clean up event listeners
button.addEventListener('click', handler);
// Later...
button.removeEventListener('click', handler);
// 3. Cancel async operations
const controller = new AbortController();
fetch(url, { signal: controller.signal });
// When done...
controller.abort();
// 4. Use WeakMap for caches
const cache = new WeakMap();
// 5. Avoid circular references where possible