Memory Management
Prevent memory leaks and optimize memory usage through proper cleanup patterns, profiling, and garbage collection awareness.
You are an expert in memory leak prevention and management for optimizing application performance. ## Key Points - **Garbage collection (GC):** JavaScript engines use mark-and-sweep GC — objects unreachable from root references are collected. Leaks occur when unintended references keep objects reachable. - **Heap snapshots:** A snapshot of all objects in memory at a point in time. Comparing two snapshots reveals objects that were allocated but never released. - **Detached DOM nodes:** DOM elements removed from the document tree but still referenced in JavaScript. A common source of leaks in single-page applications. - **Closures retaining scope:** A closure captures its enclosing scope. If a long-lived closure references a large object, that object cannot be garbage collected. - **WeakRef / WeakMap:** References that do not prevent garbage collection. Useful for caches and observer patterns where you want automatic cleanup. - **Chrome DevTools Memory tab:** Take heap snapshots before and after a user workflow. The "Comparison" view highlights objects allocated between snapshots that were not released. - **Performance Monitor (DevTools):** Real-time view of JS heap size, DOM node count, and event listener count. A steadily rising heap graph signals a leak. - **Node.js `--inspect`:** Connect Chrome DevTools to a running Node process to take heap snapshots and profiles remotely. - **Allocation timeline:** Records every allocation over time. Bars that remain (not collected) indicate potential leaks. Filter by constructor name to find the leaking object type. - Always return cleanup functions from `useEffect` in React — clear intervals, remove listeners, abort fetches, and close connections. - Use `WeakMap` and `WeakRef` for caches and observer registries to allow automatic garbage collection of unused entries. - Set a memory budget for your application (e.g., < 100MB heap for a frontend SPA) and alert when it is exceeded in production monitoring.
skilldb get performance-optimization-skills/Memory ManagementFull skill: 173 linesMemory Management — Performance Optimization
You are an expert in memory leak prevention and management for optimizing application performance.
Core Philosophy
Overview
Memory leaks occur when an application retains references to objects that are no longer needed, preventing the garbage collector from reclaiming that memory. Over time, leaks cause increasing memory consumption, degraded performance, and eventually application crashes. Effective memory management requires understanding reference lifecycles, using profiling tools, and applying disciplined cleanup patterns.
Core Concepts
- Garbage collection (GC): JavaScript engines use mark-and-sweep GC — objects unreachable from root references are collected. Leaks occur when unintended references keep objects reachable.
- Heap snapshots: A snapshot of all objects in memory at a point in time. Comparing two snapshots reveals objects that were allocated but never released.
- Detached DOM nodes: DOM elements removed from the document tree but still referenced in JavaScript. A common source of leaks in single-page applications.
- Closures retaining scope: A closure captures its enclosing scope. If a long-lived closure references a large object, that object cannot be garbage collected.
- WeakRef / WeakMap: References that do not prevent garbage collection. Useful for caches and observer patterns where you want automatic cleanup.
Implementation Patterns
Clean Up Event Listeners
Before (leaks on component unmount):
function ChatWidget() {
useEffect(() => {
const handler = (e) => handleMessage(e.data);
window.addEventListener('message', handler);
// Missing cleanup — listener persists after unmount
}, []);
}
After:
function ChatWidget() {
useEffect(() => {
const handler = (e) => handleMessage(e.data);
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
}, []);
}
Clear Timers and Subscriptions
Before:
function PollingComponent({ url }) {
useEffect(() => {
const id = setInterval(() => fetch(url), 5000);
// Interval runs forever, even after unmount
}, [url]);
}
After:
function PollingComponent({ url }) {
useEffect(() => {
const controller = new AbortController();
const id = setInterval(() => {
fetch(url, { signal: controller.signal });
}, 5000);
return () => {
clearInterval(id);
controller.abort();
};
}, [url]);
}
Avoid Detached DOM References
Before:
const elements = [];
function createListItem(text) {
const li = document.createElement('li');
li.textContent = text;
elements.push(li); // retains reference even after removal from DOM
list.appendChild(li);
}
function clearList() {
list.innerHTML = '';
// elements array still holds references to detached nodes
}
After:
function createListItem(text) {
const li = document.createElement('li');
li.textContent = text;
list.appendChild(li);
// No external array — let the DOM be the single source of truth
}
function clearList() {
list.innerHTML = '';
// All references released
}
Use WeakMap for Metadata Caches
// Strong map — leaks if objects are discarded elsewhere
const metadata = new Map();
// WeakMap — entries are automatically collected when keys are GC'd
const metadata = new WeakMap();
function annotate(obj, data) {
metadata.set(obj, data);
// When obj is no longer referenced elsewhere, both the key and value are collected
}
Node.js — Monitor Process Memory
function logMemory() {
const { heapUsed, heapTotal, rss } = process.memoryUsage();
console.log({
heapUsed: `${(heapUsed / 1024 / 1024).toFixed(1)} MB`,
heapTotal: `${(heapTotal / 1024 / 1024).toFixed(1)} MB`,
rss: `${(rss / 1024 / 1024).toFixed(1)} MB`,
});
}
// Log every 30 seconds to detect trends
setInterval(logMemory, 30000);
Measurement & Monitoring
- Chrome DevTools Memory tab: Take heap snapshots before and after a user workflow. The "Comparison" view highlights objects allocated between snapshots that were not released.
- Performance Monitor (DevTools): Real-time view of JS heap size, DOM node count, and event listener count. A steadily rising heap graph signals a leak.
- Node.js
--inspect: Connect Chrome DevTools to a running Node process to take heap snapshots and profiles remotely. - Allocation timeline: Records every allocation over time. Bars that remain (not collected) indicate potential leaks. Filter by constructor name to find the leaking object type.
Best Practices
- Always return cleanup functions from
useEffectin React — clear intervals, remove listeners, abort fetches, and close connections. - Use
WeakMapandWeakReffor caches and observer registries to allow automatic garbage collection of unused entries. - Set a memory budget for your application (e.g., < 100MB heap for a frontend SPA) and alert when it is exceeded in production monitoring.
Common Pitfalls
- Storing references to DOM nodes or component instances in module-level variables or singletons, which survive across route navigations in SPAs and accumulate over time.
- Growing in-memory caches without an eviction policy (LRU, TTL, or max-size cap), causing unbounded memory growth proportional to usage duration.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add performance-optimization-skills
Related Skills
Bundle Optimization
Reduce JavaScript bundle size through tree shaking, dependency analysis, and build configuration tuning.
Caching Strategies
Design effective browser and CDN caching strategies using Cache-Control headers, ETags, service workers, and edge caching.
Code Splitting
Reduce initial load time with route-based and component-level code splitting using dynamic imports and framework-specific patterns.
Core Web Vitals
Optimize Largest Contentful Paint, First Input Delay, and Cumulative Layout Shift for better user experience and search rankings.
Database Query Optimization
Optimize database queries through indexing, query planning, N+1 elimination, connection pooling, and schema design.
Image Optimization
Optimize image delivery with modern formats, responsive sizing, lazy loading, and CDN-based transformations.