Suggest an editImprove this articleRefine the answer for “How do you avoid memory leaks?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**No reference means no leak.** Anything reachable from the roots (global variables, closures, the DOM, caches) will not be garbage-collected. **Key point:** you need to shorten objects' lifetimes and break connections in time - handlers, timers, caches, DOM references.Shown above the full answer for quick recall.Answer (EN)Image## 1) Basic principles - **No reference means no leak.** Anything reachable from the roots (global variables, closures, the DOM, caches) will not be garbage-collected. - **Shorten objects' lifetimes** and break connections in time (handlers, timers, caches, DOM references). ## 2) Typical sources of leaks and how to close them ### Event handlers / subscriptions **Problem:** dangling listeners keep the DOM/data alive. ```javascript const onClick = () => {}; button.addEventListener('click', onClick); // ... button.removeEventListener('click', onClick); // must be the same callback ``` - In React: always do **cleanup in** `useEffect`. ```javascript useEffect(() => { const onScroll = () => {}; window.addEventListener('scroll', onScroll); return () => window.removeEventListener('scroll', onScroll); // }, []); ``` - In Nest/EventEmitter: unsubscribe in `onModuleDestroy()`. ### Timers and intervals ```javascript const id = setInterval(tick, 1000); // ... clearInterval(id); // ``` - Node 20+: use **AbortSignal** for timers: ```javascript const ac = new AbortController(); setTimeout(cb, 5000, { signal: ac.signal }); // ... ac.abort(); // ``` ### Unfinished fetch requests/streams - Cancel via `AbortController`, close streams. ```javascript const c = new AbortController(); const res = await fetch(url, { signal: c.signal }); // ... c.abort(); // ``` ### "Detached" DOM nodes - Don't keep references to nodes removed from the DOM. - Null out DOM caches after `remove()`: ```javascript elem.remove(); elemRef = null; // give the GC a chance ``` ### Large closures - Don't close over **large objects** unnecessarily. Pass **the minimum** into callbacks. - Break references after use: ```javascript let big = getHugeObject(); doWork(() => { /* use only the small part */ }); big = null; // ``` ### Global singletons/caches - Anything in a module/global scope lives "forever". Limit its size, add cleanup, or use weak references. ## 3) Leak-free caches - Object keys → **WeakMap/WeakSet** (don't block the GC): ```javascript const memo = new WeakMap(); function compute(obj) { if (memo.has(obj)) return memo.get(obj); const val = heavy(obj); memo.set(obj, val); return val; } ``` - For strings/numbers use an **LRU** with a limit: ```javascript class LRU { constructor(limit=500){ this.limit=limit; this.map=new Map(); } get(k){ const v=this.map.get(k); if(v){ this.map.delete(k); this.map.set(k,v); } return v; } set(k,v){ if(this.map.has(k)) this.map.delete(k); this.map.set(k,v); if(this.map.size>this.limit){ const oldest = this.map.keys().next().value; this.map.delete(oldest); // evict } } } ``` ## 4) React / Next.js - **Clean up** all side effects (`useEffect` → `return () => {...}`). - Don't store huge objects in `useRef`/context unnecessarily. - Avoid endless microtask chains (chained `Promise`s), they delay GC and rendering. - Unsubscribe from **Observable/WebSocket** in cleanup. - For large lists, use virtualization (fewer objects at once). ## 5) Node.js / NestJS - Close connections/pools: - Prisma: `await prisma.$disconnect()` on shutdown. - Redis/Mongo/pg: `.quit()`/`.close()`/`.end()` in `onModuleDestroy()`. - Streams: always `.destroy()`/`.end()` on errors and timeouts. - EventEmitter: `emitter.removeListener`/`removeAllListeners` on unload. - Avoid unbounded `setInterval`; use `setTimeout` **+ a loop** with checks/abort. - Handle signals: ```javascript process.on('SIGTERM', async () => { await app.close(); // close the server/DB/queues process.exit(0); }); ``` ## 6) Async work and queues - Set **timeouts** and **concurrency limits** (p-limit, semaphores). - For networking/streams, use **backpressure**: don't read faster than you process. - Don't keep large arrays of promises in memory; process them in chunks. ## 7) FinalizationRegistry - for soft cleanup only - Good for external caches/handles, but **don't rely on the order/timing** of the call. - Don't build business logic on finalizers. ## 8) Tools for finding leaks ### Browser (Chrome) - **Memory → Heap snapshot**: look for "Detached HTML elements", retainers. - **Allocation instrumentation**: who creates and retains objects. - **Performance**: long microtasks/frequent GC are a sign of trouble. ### Node - `--inspect` + Chrome DevTools → Heap snapshots. - `heapdump`/`clinic` for snapshots in production. - GC logs: `node --trace-gc app.js` (for diagnostics, not in prod). - Metrics: monitor RSS/heapUsed, alert on a growth trend. ## 9) Guardrails in code and CI - Limits on cache/queue sizes. - An E2E test: "warm it up N times → memory stabilizes". - Linter rules: forbid anonymous listeners where removal is needed, forbid `setInterval` without an explicit clear.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.